Deno で試すデータベースアクセス(supabase編)

以前、supabase Edge Functions でデータベースアクセスを試していました。
supabase Edge Functions だと、環境変数でキー情報を提供するので便利ではありますが、Denoから直接使用する方法を試していなかったので、試していきます。

ローカル Docker の supabase で使う

supabase の DB 、結局 postgreSQL なので準備はだいたい同じになる。

環境準備

いつものDockerで準備。

Dockerfile
1
2
3
4
5
6
7
8
9
FROM denoland/deno:1.25.1

RUN apt-get update

RUN mkdir /usr/src/app
WORKDIR /usr/src/app


EXPOSE 8080
docker-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
version: "3"
services:
app:
build:
context: .
dockerfile: Dockerfile
privileged: true
command: tail -f /dev/null
ports:
- "8080:8080"
- "35729:35729"
volumes:
- .:/usr/src/app:cached
tty: true
supabase:
image: "supabase/postgres"
ports:
- "5432:5432"
- "3000:3000"
volumes:
- "./supabase/postgresql/data/postgres:/var/lib/postgresql/data"
environment:
POSTGRES_PASSWORD: postgres
pgadmin:
image: dpage/pgadmin4
restart: always
ports:
- 8081:80
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-test@test.com}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-password}
volumes:
- ./pgadmin:/var/lib/pgadmin
depends_on:
- supabase

接続してみる

次のソースコードで、接続を確認。 (以前、postgreSQL 接続を試した際のソースと同じ)

sample1.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import "https://deno.land/std@0.133.0/dotenv/load.ts";
import { Client } from "https://deno.land/x/postgres/mod.ts";

const client = new Client({
hostname: Deno.env.get("POSTGRES_HOST"),
port: 5432,
database: Deno.env.get("POSTGRES_DB"),
user: Deno.env.get("POSTGRES_USER"),
password: Deno.env.get("POSTGRES_PASSWORD"),
});

await client.connect();

interface User {
id: number;
name: string;
created_at: Date;
updated_at: Date;
}

async function createUserTable() {
await client.queryObject({
text: `
CREATE TABLE IF NOT EXISTS users (
id serial primary key,
name text,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
`,
});
}

async function createUserFunction() {
await client.queryObject({
text: `
DROP FUNCTION IF EXISTS set_update_time() CASCADE;
CREATE FUNCTION set_update_time() RETURNS trigger AS '
begin
new.updated_at := ''now'';
return new;
end;
' LANGUAGE plpgsql;
CREATE TRIGGER update_trigger BEFORE UPDATE ON public.users FOR EACH ROW EXECUTE PROCEDURE set_update_time();
`,
});
}

async function getUsers(): Promise<any[][]> {
const users = await client.queryArray({
text: `
select
id,
name,
created_at,
updated_at
from
public.users
order by
id
`,
});
return users.rows;
}

async function getUsersObject(): Promise<User[]> {
const users =
await client.queryObject<User>(
{
text: `
select
id,
name,
created_at,
updated_at
from public.users
order by id
`,
fields: ["id", "name", "created_at", "updated_at"],
});
return users.rows;
}

async function insertUser({ name }: { name: string }) {
const users = await client.queryObject({
text: `
insert into public.users
(name)
values
($1)
`,
args: [name],
fields: ["name"],
});
return users;
}

async function updateUser(id: number, { name }: { name: string }) {
const users = await client.queryObject({
text: `
update
public.users
set
name = $1
where
id = $2
`,
args: [name, id],
fields: ["name"],
});
return users;
}

// テーブル作成
await createUserTable();

// 関数を設定
await createUserFunction();

// データを挿入
await insertUser({ name: "AAA1" });
await insertUser({ name: "AAA2" });

await getUsersObject()

// データ更新
await updateUser(1, { name: "BBB11" });

// データ取得
console.log(await getUsers());
console.log(await getUsersObject());

// 接続切断
await client.end();

.env の中身は次の通り。

.env
1
2
3
4
POSTGRES_HOST=supabase
POSTGRES_DB=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres

実行すると、次の通り。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
deno run --allow-net --allow-env --allow-read --unstable sample1.ts
[
{
id: 1,
name: "BBB1",
created_at: 2022-04-03T18:02:29.743Z,
updated_at: 2022-04-03T18:02:29.749Z
},
{
id: 2,
name: "AAA2",
created_at: 2022-04-03T18:02:29.747Z,
updated_at: 2022-04-03T18:02:29.747Z
}
]

と、以上の通りでいい。

nessie でローカル Docker の supabase に マイグレーションする

supabase はテーブルエディターでテーブルの作成はできるが、可能ならマイグレーションツールで管理したい。 なので
nessieで試していきます。

1
2
3
4
5
6
7
$ deno run -A --unstable https://deno.land/x/nessie/cli.ts init --mode folders
# => db/migrate
# db/seeds が作成される
#

$ deno run -A --unstable https://deno.land/x/nessie/cli.ts init --mode config --dialect pg
# => nessie.config.ts が作成される

nessie.config.ts は、次のように書き換えしておく。

nessie.config.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import "https://deno.land/std@0.133.0/dotenv/load.ts"; // deotenv を導入、不要ならスキップ
import {
ClientPostgreSQL,
NessieConfig,
} from "https://deno.land/x/nessie@2.0.7/mod.ts";

const client = new ClientPostgreSQL({ // 環境変数参照に変更
database: Deno.env.get("POSTGRES_DB"),
hostname: Deno.env.get("POSTGRES_HOST"),
port: 5432,
user: Deno.env.get("POSTGRES_USER"),
password: Deno.env.get("POSTGRES_PASSWORD"),
});

/** This is the final config object */
const config: NessieConfig = {
client,
migrationFolders: ["./db/migrations"],
seedFolders: ["./db/seeds"],
};

export default config;

引き続き、マイグレーションファイルを作成。

1
2
$ deno run -A --unstable https://deno.land/x/nessie/cli.ts make:migration create_users
$ deno run -A --unstable https://deno.land/x/nessie/cli.ts make:migration create_users_function

マイグレーションファイルは次のように編集します。

[数字列]_create_users.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { AbstractMigration, Info, ClientPostgreSQL } from "https://deno.land/x/nessie@2.0.7/mod.ts";

export default class extends AbstractMigration<ClientPostgreSQL> {
/** Runs on migrate */
async up(info: Info): Promise<void> {
await this.client.queryArray(
`
CREATE TABLE IF NOT EXISTS public.users (
id serial primary key,
name text,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
`);
}

/** Runs on rollback */
async down(info: Info): Promise<void> {
await this.client.queryArray("DROP TABLE public.users;");
}
}
[数字列]_create_users_function.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import {
AbstractMigration,
ClientPostgreSQL,
Info,
} from "https://deno.land/x/nessie@2.0.7/mod.ts";

export default class extends AbstractMigration<ClientPostgreSQL> {
/** Runs on migrate */
async up(info: Info): Promise<void> {
await this.client.queryArray(`
CREATE FUNCTION set_update_time() RETURNS trigger AS '
begin
new.updated_at := ''now'';
return new;
end;
' LANGUAGE plpgsql;
CREATE TRIGGER update_trigger BEFORE UPDATE ON public.users FOR EACH ROW EXECUTE PROCEDURE set_update_time();
`);
}

/** Runs on rollback */
async down(info: Info): Promise<void> {
await this.client.queryArray(
"DROP FUNCTION IF EXISTS set_update_time() CASCADE;",
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
$ deno run -A --unstable https://deno.land/x/nessie/cli.ts migrate
Starting migration of 2 files
----

Migrating 20220916004331_create_users.ts
Done in 0.04 seconds
----

Migrating 20220916004415_create_users_function.ts
Done in 0.01 seconds
----

Migrations completed in 0.05 seconds

この操作によりマイグレーションできるので、先のサンプルのテーブル作成と関数登録の処理を外して実行できるようになります。

supabase のDBに接続する前提としたローカルの開発環境としては、こんなところ。

クラウド SaaS の supabase で使う

本題、SaaS(supabase 的には BaaS)のsupabaseに接続したい。

特に、特別なことは無かった

先のサンプルで、環境変数を参照する形に変更していますが、パラメーターを変えていくだけでした。

supabase に作成したプロジェクトの設定を参照していきます。

[使用したい organization] => [使用したい project] => Setttings => Database => Connection info

こちらを参照して接続設定を修正すると、supabaseのDBに直接接続/マイグレーションができます。

  • user名とデータベース名を変える操作は、設定の中で見つからなかったので、変更できない可能性がある。
  • データベースのパスワードは生成されたものが割と文字数が少なく感じるので、気にするなら任意の長いものを使った方が良さそう。

ここまで確認してきました。
しかし、supabase-cli は、マイグレーション機能を持っているということで、無理にnessie使わなくてもよかったね。

ということに一通り、終わってから気が付いたのでした。

では。