Astro DB を使ったアプリケーションを Vercel にデプロイする
まえがき
この記事は、Astro DB / Astro Studio を使ってページごとに押された「いいね」を管理する Web サイトを Vercel にデプロイするまでの流れを簡単に紹介する記事です。
バージョン情報
"@astrojs/check": "^0.5.10",
"@astrojs/db": "^0.10.4",
"@astrojs/preact": "^3.2.0",
"@astrojs/vercel": "^7.5.3",
"astro": "^4.6.1",
"preact": "^10.20.2",
"typescript": "^5.4.5"
Node.js Version: 20.x
完成品
https://github.com/k1350/astro-db-test/
見た目を全く整えていません。ご了承ください。
開発手順
以下、パッケージマネージャーに pnpm を使っている想定でコマンドを書いているので、pnpm 以外の人は適当に読み替えてください。
また可能な限り公式ドキュメントへのリンクを張るので、不明点は公式ドキュメントをご参照ください。
1. Astro プロジェクトを作る
下記コマンドで Astro プロジェクトを作ります。
pnpm create astro@latest
Ref. https://docs.astro.build/en/install/auto/
可能な限り最小限のコードで作るため、テンプレートは Empty にしました。
今回はページごとに押された「いいね」数を管理したいので、トップページの他に About ページも作っておきます。
この時点のソースコード:
https://github.com/k1350/astro-db-test/tree/dd630a013b0088d6ebcfc3ccbabb3b4870c0da00
2. Astro DB 追加
下記コマンドで Astro DB を追加します。
pnpm astro add db
Ref. https://docs.astro.build/en/guides/astro-db/
この時点のソースコード:
https://github.com/k1350/astro-db-test/commit/124d68dcf0f9db6d035204b0db5b34cb9510e99f
3. 「いいね」数を表示する
Astro DB でページごとの「いいね」数を管理するため、テーブルを定義します。
今回は『匿名で誰でも何度でも「いいね」できる』仕様として、各ページでいつ「いいね」されたかだけを記録するものとします。
import { column, defineDb, defineTable } from 'astro:db';
const Like = defineTable({
columns: {
id: column.number({ primaryKey: true }),
url: column.text(),
createdAt: column.date(),
}
})
// https://astro.build/db/config
export default defineDb({
tables: { Like }
});
Ref. https://docs.astro.build/en/guides/astro-db/#define-your-database
また「いいね」を追加する実装を行うまでの間は seed に値をセットしておくことにします。
import { Like, db } from 'astro:db';
// https://astro.build/db/seed
export default async function seed() {
await db.insert(Like).values([
{ url: "/", createdAt: new Date() },
{ url: "/", createdAt: new Date() },
{ url: "/about", createdAt: new Date() },
])
}
Ref. https://docs.astro.build/en/guides/astro-db/#seed-your-database
そして「いいね」数を表示するコンポーネントを作成します。
count
のコード例は Drizzle ORM のドキュメントに載っています。
---
import { count, db, eq, Like } from 'astro:db';
const currentPath = Astro.url.pathname;
const likeCounts = await db.select({ count: count() }).from(Like).where(eq(Like.url, currentPath));
const likeCount = likeCounts.length > 0 ? likeCounts[0].count : 0;
---
<p>Likes: {likeCount}</p>
単純に現在の URL に一致する url
の列数をカウントして表示しているだけですね。
likeCounts
の型が
type Result = {
count: number;
}[];
という配列なので一つ目の要素を取得しています。
このコンポーネントを各ページで読み込んで起動すると、下記のようにページごとに異なる「いいね」数が表示されます。
この時点のソースコード:
https://github.com/k1350/astro-db-test/tree/544d1cb79dc5abb0f0f5dbc07ca30d41f9bf7aa9
4. 「いいね」を追加する
「いいね」を追加する API を作ります。
(なおサンプルアプリケーションであるためセキュリティについては考慮しておりません。)
import type { APIRoute } from "astro";
import { Like, db } from "astro:db";
export const POST: APIRoute = async ({ request }) => {
const data = await request.formData();
const url = data.get("url");
if (typeof url === "string" && url.startsWith("/")) {
await db
.insert(Like)
.values({ url, createdAt: new Date() });
return new Response(
JSON.stringify({
message: "Success"
}),
{ status: 200 }
);
}
return new Response(
JSON.stringify({
message: "Bad Request"
}),
{ status: 400 }
);
};
そして上記の API にデータを投げるコンポーネントを作ります。書きやすさのために Preact を入れています。
import { useState } from "preact/hooks";
type Props = {
url: string;
}
export function AddLikeForm({ url }: Props) {
const [isSubmitting, setIsSubmitting] = useState(false);
async function submit(e: SubmitEvent) {
e.preventDefault();
setIsSubmitting(true);
const formData = new FormData(e.target as HTMLFormElement);
const response = await fetch("/api/addLike", {
method: "POST",
body: formData,
});
if (response.status === 200) {
// 更新後の「いいね」数を表示するためにページ全体をリロードしていますが、
// 劣悪なユーザー体験になるので真似しないでください
window.location.reload();
}
setIsSubmitting(false);
}
return (
<form onSubmit={submit}>
<input type="hidden" name="url" value={`${url}`} />
<button type="submit" disabled={isSubmitting}>Add Like</button>
</form>
);
}
上記のコンポーネントを LikeCount.astro 内で読み込みます。
---
import { count, db, eq, Like } from 'astro:db';
+ import { AddLikeForm } from './AddLikeForm';
const currentPath = Astro.url.pathname;
const likeCounts = await db.select({ count: count() }).from(Like).where(eq(Like.url, currentPath));
const likeCount = likeCounts.length > 0 ? likeCounts[0].count : 0;
---
<p>Likes: {likeCount}</p>
+ <AddLikeForm url={`${Astro.url.pathname}`} client:load />
client:load
と書かないと動かないので注意してください。(1敗)
Ref. https://docs.astro.build/en/guides/astro-db/#insert
また astro.config.mjs で output: 'server'
または output: 'hybrid'
の指定を追加する必要もあります。
ここまでで「いいね」ができるようになりました。
"Add Like" ボタンを押すと「いいね」数が 1 ずつ増加するはずです。
この時点のソースコード:
https://github.com/k1350/astro-db-test/tree/8302fbfef9e09dff54efb01cc387362c98b7e309
5. Astro Studio と連携する
データ永続化のため、Astro Studio を使います。ここまでのソースコードを GitHub に push してから Astro Studio のアカウントを作ります。
https://studio.astro.build/auth/login
Sign up リンクを押すといかにもメールアドレスで登録できそうな入力欄が出てくるのですが、2024/4/14 現在、押せるボタンが "Continue with GitHub" しかないので GitHub 連携以外で登録するのは不可能みたいです。[1]
アカウント作成後 "Create project" ボタンを押して "Import from GitHub repo" ボタンからプロジェクトを作成します。リージョンは日本を選んでおきます。
Ref. https://docs.astro.build/en/recipes/studio/#create-a-new-studio-project
なお Astro Studio の Web UI からプロジェクトを作ると Set up the GitHub CI Action に記載のワークフローが勝手に作られるのですが(トークンも勝手に作られて勝手に設定される)、このワークフローが pnpm を正しく認識してくれなかったので私は下記のように直しました。
6. Vercel にデプロイする
いよいよデプロイします。今回のデプロイ先は Vercel にしました。
下記コマンドでアダプターをインストールします。
pnpm astro add vercel
Ref. https://docs.astro.build/en/guides/integrations-guide/vercel/
Astro Studio に接続するためにトークンが必要になりますので、Astro Studio の Settings > Tokens からトークンを作成してください。
あとは GitHub にソースコードを push してから Vercel に連携します。
Astro Studio 特有の注意点は二点です。
- Vercel の Settings > Environment Variables に
ASTRO_STUDIO_APP_TOKEN
という名称でトークンをセットする必要があります - Vercel の Settings > Build & Development Settings で Build Command を
astro build --remote
で上書きする必要があります
デプロイが成功したら動作確認します。"Add Like" ボタンを押下すると「いいね」数が増えて
Astro Studio のほうにもデータが連携されると思います。
この時点のソースコード:
https://github.com/k1350/astro-db-test/tree/ac383c225f2cc376c62690116a8e06809098900c
開発手順は以上です。
余談
実運用にあたってはステージング環境用など、複数の DB が欲しいですよね。
同じリポジトリから二つ以上の Astro DB プロジェクトを作ることは可能なのだろうか? と思って "Import from GitHub repo" ボタンから既にプロジェクト作成済みのリポジトリをもう一度 import してみたところ、バグりました。
- プロジェクトが二つできているように見えるが、どちらも後から作成したプロジェクトの内容が表示される
- たとえば片方のプロジェクトにトークンを追加するともう片方のプロジェクトにも同じトークンができる
- 元々あったほうのプロジェクトのデータ・トークン等の一切は参照できなくなる
- 片方消すと元のデータ・トークンが戻ってきたため、どうやら内部的には異なる二つのプロジェクトが生成されていたようだが、フロントから参照する際には同一名称で後に作られたプロジェクトを常に取得してしまうらしい
何の警告もなしに import できる割にバグるのでご注意ください。
-
メールアドレスで登録できそうでできない謎の UI
↩︎
ちょっと株式会社(chot-inc.com)のエンジニアブログです。 フロントエンドエンジニア募集中! カジュアル面接申し込みはこちらから chot-inc.com/recruit/iuj62owig
Discussion