Astro DBをAstro以外で使う
TL;DR
- Astro DBはDrizzle LibSQL(SQLite)互換
- 内部でAPIにSQLを送信している
- 仕組み上、Astroなしで無理矢理使うことができるがアンドキュメンテッドなのでお勧めしない
Astro DBとは
Astro DBはAstroが提供するフルマネージドなSQLデータベースです。Astro Studioというプラットフォームの一部で、Astroで構築するウェブサイトのバックエンドのDBとして利用できます。
ユースケースとしてはウェブサイトの問い合わせの保存先やコンテンツのマスターデータの管理などを想定していそうです
使い方は以下のドキュメントに載っています
静的なSSGでも動的なSSRでも使えます
以下のstudio-templatesリポジトリにサンプルプロジェクトがあります
Astro DBのアーキテクチャ
Astro DBはDrizzle ORMを拡張してAstroで構築したサイトにDBアクセスのインターフェイスを追加します
内部のvite build過程でスキーマ定義に応じたDBクライアントが生成されます
DBクライアントはlibSQLというSQLiteをデータベースサーバーにするためのフォークバージョンのドライバーに基いています
DBクライアントからのSQLiteへの操作が発生すると、Astro StudioがホストするlibSQLのエンドポイントへHTTPでSQLを通信して結果をJSONで得るというのがサービスのアーキテクチャの概要です
Astro DBのAPIを直接使ってみる
ここまでの知識から「Astro DBはAstroフレームワークの外でも使えるのではないか?」と思い付いたので検証してみます
まずDBのホストが https://db.services.astro.build
であることが分かります
/db/query
のエンドポイントにアクセストークンを付けて呼び出します。アクセストークンは https://studio.astro.build/ から取得できます
SQLiteへの操作にHTTPリクエストで介入するための drizzle-orm/sqlite-proxy
を使います
レスポンスのJSONをパースすると結果が得られます
import {z} from "zod"
const remoteResultSchema = z.object({
columns: z.array(z.string()),
columnTypes: z.array(z.string()),
rows: z.array(z.array(z.unknown())),
rowsAffected: z.number(),
lastInsertRowid: z.unknown().optional()
});
これで const db = createAstroDb(c.env.ASTRO_DB_TOKEN)
とするとDBクライアントが生成できるようになりました
あとはこれを自分のアプリケーションに組込みます。DBの読み書きが確認できるのでシンプルな掲示板サイトを作りました
https://you-dont-need-astrodb-with-astrojs.pages.dev/ (URLは気にしないでください)
いつものようにHonoでウェブアプリを作ります
npm create hono@latest
npm i drizzle-orm zod
DBスキーマを以下のように定義しました
import { column, defineDb, defineTable, NOW } from 'astro:db';
export const Board = defineTable({
columns: {
id: column.number({primaryKey: true}),
name: column.text(),
message: column.text(),
created_at: column.date({ default: NOW }),
},
});
export default defineDb({
tables: {
Board,
},
});
db push
で反映します。MY_LINK_IDはAstro DBのデータベースIDで https://studio.astro.build/ から取得できます
npx astro db link $MY_LINK_ID
npx astro db push
DBクライアントはDrizzle ORMのAPIがそのまま使えます
app.get('/', async (c) => {
const db = createAstroDb(c.env.ASTRO_DB_TOKEN)
const result = await db.run(
sql.raw('SELECT id, name, message FROM Board ORDER BY created_at LIMIT 100')
) as [number, string, string][]
// ...
フォームのPOST先からDBにINSERTする処理も書いておく
app.post('/board', async (c) => {
const {name, message} = await c.req.parseBody()
const db = createAstroDb(c.env.ASTRO_DB_TOKEN)
await db.run(
sql`INSERT INTO Board (name, message)
VALUES (${name}, ${message})`
)
return c.redirect('/')
})
ソースコード全体
Tips: リモートでSQL実行する
--remote
をつけるとAstro StudioのDBに対する操作で、つけない時はsqliteファイルが.astro/
ディレクトリ以下にあります
npx astro db shell --query 'select * from Board' --remote
npx astro db execute db/seed.ts --remote
Discussion