👋

SupabaseCLIでローカル環境構築

2023/06/16に公開

概要

Supabase CLI | Supabase Docs

Supabase CLIは、開発者がSupabaseのプロジェクトをローカル環境で構築、管理、デバッグしたり、app.supabase.com のプロジェクトとリンクした管理できるツールです。

この記事では、Supabase CLIの設定と使用方法から始め、データベースのマイグレーション手法、そしてローカルで起動したSupabase環境へのクライアントからの接続までを試した際の記事になります。

検証環境

$ sw_vers
ProductName:     macOS
ProductVersion:  13.4
BuildVersion:    22F66

$ supabase --version
1.64.8

インストール

npmパッケージや、Mac Windows Linux 向けにバイナリが提供されています。

https://supabase.com/docs/guides/cli#installation

今回はMacの環境なのでHomebrewを使ってインストールしていきます。

brew install supabase/tap/supabase

Local Development

上記を参考にしながらローカルでのSupabase環境の構築を進めて、CLIを試していきたいと思います。

supabase login

https://supabase.com/docs/reference/cli/supabase-login

Supabaseでアカウント登録したアカウントに対してログインを行う事ができます。

特にプロジェクトをリンクさせる必要がなく、ローカルだけで完結する場合は必要ないです。

事前にこちらからAccessTokenを生成しておきます。

image1

コマンドを実行するとAccessTokenが求められるのでペーストしログインしておきます。

supabase login
You can generate an access token from https://app.supabase.com/account/tokens
Enter your access token:
Finished supabase login.

認証情報はMacOSだとKeychainに保存されていました。

supabase projects list で該当アカウントのプロジェクト一覧が表示されていればOKです。


supabase init

https://supabase.com/docs/reference/cli/supabase-init

次にローカルプロジェクトを上記コマンドで初期化します。

試しに適当なディレクトリで作成するといかの様に作成されました。

Generate VS Code workspace settingsはYesで作成

$ supabase init         
Generate VS Code workspace settings? [y/N] y
Open the supabase-cli.code-workspace file in VS Code.
Finished supabase init.
$ tree           
.
├── supabase
│   ├── config.toml
│   ├── functions
│   └── seed.sql
└── supabase-cli.code-workspace

次に Generate VS Code workspace settings をNoで作成すると

$ supabase init
Generate VS Code workspace settings? [y/N] N
Finished supabase init.
$ tree
.
└── supabase
    ├── config.toml
    └── seed.sql

↑になります。

違いはそのままVSCodeのworkspaceの設定があるかないかの違いだけです。

ちなみに作成された code-workspace は以下の様になっていました。

{
  "folders": [
    {
      "name": "project-root",
      "path": "./"
    },
    {
      "name": "supabase-functions",
      "path": "supabase/functions"
    }
  ],
  "settings": {
    "files.exclude": {
      "supabase/functions/": true
    }
  }
}

supabase start

https://supabase.com/docs/reference/cli/supabase-start

Supbaseのローカル環境で必要な全コンテナを起動します。

自分の環境だと以下のimageがpullされていました。(2023/06/10の内容です)

public.ecr.aws/supabase/edge-runtime          v1.4.2             147MB
public.ecr.aws/supabase/postgres-meta         v0.66.0            412MB
public.ecr.aws/supabase/studio                20230512-ad596d8   268MB
public.ecr.aws/supabase/gotrue                v2.62.1            41.1MB
public.ecr.aws/supabase/postgres              15.1.0.73          852MB
public.ecr.aws/supabase/storage-api           v0.37.4            327MB
public.ecr.aws/supabase/logflare              1.0.2              395MB
public.ecr.aws/supabase/realtime              v2.10.1            180MB
public.ecr.aws/supabase/vector                0.28.1-alpine      124MB
public.ecr.aws/supabase/postgrest             v10.1.2            16.1MB
public.ecr.aws/supabase/imgproxy              v3.8.0             173MB
public.ecr.aws/supabase/kong                  2.8.1              139MB
public.ecr.aws/supabase/pgadmin-schema-diff   cli-0.0.5          244MB
public.ecr.aws/supabase/inbucket              3.0.3              25.8MB
public.ecr.aws/supabase/migra                 3.0.1621480950     66.5MB

コンテナ起動し、 supabase status で現在の起動状態を確認できます。

この中の Studio URL のURLにアクセスすると見慣れたDashboardが確認できます。


supabase stop

https://supabase.com/docs/reference/cli/supabase-stop

停止する際はこのコマンドで全コンテナを停止できます。


Database migrations

https://supabase.com/docs/guides/getting-started/local-development#database-migrations

手元で employees テーブルを作成するSQLを用意して、ローカル環境のDatabaseに反映してみたいと思います。

以下コマンドを実施します。

supabase migration new create_employees_table

./supabase/migrations ディレクトリ内に XXXXXXXXXXXXXX_create_employees_table.sql ファイルが作成されるので以下に修正します。

create table
employees (
id bigint primary key generated always as identity,
name text,
email text,
created_at timestamptz default now()
);

早速ローカル環境のDatabaseに以下コマンドで反映させてみます。

$ supabase db reset
Resetting local database...
Initializing schema...
Applying migration XXXXXXXXXXXXXX_create_employees_table.sql...
Seeding data supabase/seed.sql...
Restarting containers...
Finished supabase db reset on branch main.

Studio URL のURLにアクセスしダッシュボードの Table Editor を確認すると employees テーブルが作成されているかと思います。

image2

次にSeedデータを投入してみたいと思います。

supabase/seed.sql ファイル(存在しない場合は作成) を以下に修正します。

insert into
public.employees (name)
values
('Erlich Bachman'),
('Richard Hendricks'),
('Monica Hall');

再度 supabase db reset を実行しダッシュボードで確認するとデータが投入されているかと思います。

image3

クライアントからの接続

次にローカルに構築したSupabaseの環境に対してクライアントから接続してみたいと思います。

クライアントとしての検証環境は以下になります。 @supabase/supabase-js を使って検証します。

- next: v13.4.4
- react: v18.2.0
- chakra-ui/react: v2.7.0
- @supabase/supabase-js: v2.24.0

まずはSupabaseに接続するclientを作成する為、以下内容で libs/supabase.ts に作成します。

import { createClient } from '@supabase/supabase-js';

const supabaseUrl = 'http://localhost:54321'
const supabaseKey = '...'

export const supabase = createClient(supabaseUrl, supabaseKey);

supabaseKey には supabase start 時に表示される anon key を設定します。

Auth

ダッシュボードのAuthenticationから新規にユーザーを作成してみます。

image4

適当なメアドとパスワードを入力してユーザーを作成します。

image5

authを扱う簡易的なhooksを以下内容で作成します。

import { Session } from '@supabase/supabase-js';
import { supabase } from 'libs/supabase';
import { useCallback, useEffect, useState } from 'react';

export const useAuth = () => {
  const [session, setSession] = useState<Session>();

  useEffect(() => {
    const { data: authData } = supabase.auth.onAuthStateChange((_, session) => {
      session && setSession(session);
    });

    return () => authData.subscription.unsubscribe();
  }, []);

  const signInWithEmail = useCallback(
    async (email: string, password: string) => {
      try {
        const { error } = await supabase.auth.signInWithPassword({
          email,
          password,
        });
        if (error) {
          throw new Error(error.message);
        }
      } catch (e) {
        console.error(e);
      }
    },
    []
  );

  const signOut = useCallback(async () => {
    await supabase.auth.signOut();
  }, []);

  return {
    session,
    signInWithEmail,
    signOut,
  };
};

こちらを使って簡易的なSignInページを作って試してみます。

'use client';

import { Button, Divider, Input, VStack } from '@chakra-ui/react';
import { useAuth } from 'hooks/useAuth';
import { useRouter } from 'next/navigation';
import { useState } from 'react';

const SignIn = () => {
  const router = useRouter();
  const [email, setEmail] = useState<string>();
  const [password, setPassword] = useState<string>();
  const { session, signInWithEmail } = useAuth();
  if (session) {
    router.push('/');
  }
  return (
    <VStack w="full" justify="center" alignItems="center">
      <VStack w="40rem" p={8} spacing={4}>
        <Input
          placeholder="Email"
          type="email"
          onChange={(e) => setEmail(e.target.value)}
        />
        <Input
          placeholder="Password"
          type="password"
          onChange={(e) => setPassword(e.target.value)}
        />
        <Divider py={4} />
        <Button
          colorScheme="blue"
          onClick={() => {
            email && password && signInWithEmail(email, password);
          }}
        >
          SignIn
        </Button>
      </VStack>
    </VStack>
  );
};

export default SignIn;

image6

上記画面に作成したメアドとパスワードを入力し「SignIn」ボタンクリックするとちゃんとSignInできることが確認できるかと思います。

Database

事前に型生成を行っておきます。

supabase gen types typescript --local > types/database.types.ts

先程の createClient に作成された型情報を設定します。

export const supabase = createClient<Database>(supabaseUrl, supabaseKey);

次に確認用の画面を以下内容で作成します。

'use client';

import {
  Box,
  Table,
  TableContainer,
  Tbody,
  Td,
  Th,
  Thead,
  Tr,
} from '@chakra-ui/react';
import { supabase } from 'libs/supabase';
import { useEffect, useState } from 'react';
import { Database } from 'types/database.types';

const DatabasePage = () => {
  const [items, setItems] = useState<
    Database['public']['Tables']['employees']['Row'][]
  >([]);
  useEffect(() => {
    (async () => {
      const { data, error } = await supabase
        .from('employees')
        .select('*')
        .order('created_at');
      if (error) {
        console.error(error);
        return;
      }
      if (data) {
        setItems(data);
      }
    })();
  }, []);

  return (
    <Box minH="100vh">
      <TableContainer>
        <Table variant="simple">
          <Thead>
            <Tr>
              <Th>id</Th>
              <Th>name</Th>
              <Th>email</Th>
              <Th>created_at</Th>
            </Tr>
          </Thead>
          <Tbody>
            {items.map((item) => (
              <Tr>
                <Td>{item.id}</Td>
                <Td>{item.name}</Td>
                <Td>{item.email}</Td>
                <Td>{item.created_at}</Td>
              </Tr>
            ))}
          </Tbody>
        </Table>
      </TableContainer>
    </Box>
  );
};

export default DatabasePage;

以下の様に登録したemployeesのデータが表示されているのが確認できます。

image7

Realtime

試しに以下の2画面をそれぞれ作成します。

  • client1

    'use client';
    
    import { supabase } from 'libs/supabase';
    import { useEffect } from 'react';
    
    const channel = supabase.channel('room1');
    
    const Client1Page = () => {
      useEffect(() => {
        const subscription = channel.subscribe((status) => {
          if (status === 'SUBSCRIBED') {
            setInterval(() => {
              channel.send({
                type: 'broadcast',
                event: 'message',
                payload: { text: 'hello' },
              });
              console.log('broadcast message');
            }, 1000);
          }
        });
        return () => {
          subscription.unsubscribe();
        };
      }, []);
      return <p>Client1Page</p>;
    };
    
    export default Client1Page;
    
  • client2

    'use client';
    
    import { supabase } from 'libs/supabase';
    import { useEffect } from 'react';
    
    const Client2Page = () => {
      useEffect(() => {
        const subscription = supabase
          .channel('room1')
          .on('broadcast', { event: 'message' }, (payload) => console.log(payload))
          .subscribe((status) => {
            if (status === 'SUBSCRIBED') {
              // ...
            }
          });
        return () => {
          subscription.unsubscribe();
        };
      }, []);
      return <p>Client2Page</p>;
    };
    
    export default Client2Page;
    

それぞれのページを表示させてみると以下の様にBroadcastが送れているかと思います。

image8

参考URL

SupabaseのCLIでローカル環境を構築する(Local Development) | DevelopersIO

Discussion