🙄

Astro DBをAstro以外で使う

2024/03/14に公開

TL;DR

  • Astro DBはDrizzle LibSQL(SQLite)互換
  • 内部でAPIにSQLを送信している
  • 仕組み上、Astroなしで無理矢理使うことができるがアンドキュメンテッドなのでお勧めしない

Astro DBとは

Astro DBはAstroが提供するフルマネージドなSQLデータベースです。Astro Studioというプラットフォームの一部で、Astroで構築するウェブサイトのバックエンドのDBとして利用できます。

https://astro.build/db/

ユースケースとしてはウェブサイトの問い合わせの保存先やコンテンツのマスターデータの管理などを想定していそうです

使い方は以下のドキュメントに載っています

https://docs.astro.build/en/guides/astro-db/

静的なSSGでも動的なSSRでも使えます

以下のstudio-templatesリポジトリにサンプルプロジェクトがあります

https://github.com/withastro/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であることが分かります

https://github.com/withastro/astro/blob/973a07ff251f78ea48db840ad8573cd9213f7105/packages/db/src/core/utils.ts#L12-L15

/db/queryのエンドポイントにアクセストークンを付けて呼び出します。アクセストークンは https://studio.astro.build/ から取得できます

SQLiteへの操作にHTTPリクエストで介入するための drizzle-orm/sqlite-proxy を使います

https://github.com/withastro/astro/blob/973a07ff251f78ea48db840ad8573cd9213f7105/packages/db/src/runtime/db-client.ts#L27-L48

レスポンスのJSONをパースすると結果が得られます

https://github.com/withastro/astro/blob/973a07ff251f78ea48db840ad8573cd9213f7105/packages/db/src/runtime/db-client.ts#L50-L60

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