SupabaseCLIでローカル環境構築
概要
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を生成しておきます。
コマンドを実行すると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
テーブルが作成されているかと思います。
次にSeedデータを投入してみたいと思います。
supabase/seed.sql
ファイル(存在しない場合は作成) を以下に修正します。
insert into
public.employees (name)
values
('Erlich Bachman'),
('Richard Hendricks'),
('Monica Hall');
再度 supabase db reset
を実行しダッシュボードで確認するとデータが投入されているかと思います。
クライアントからの接続
次にローカルに構築した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から新規にユーザーを作成してみます。
適当なメアドとパスワードを入力してユーザーを作成します。
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;
上記画面に作成したメアドとパスワードを入力し「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のデータが表示されているのが確認できます。
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が送れているかと思います。
Discussion