0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Astro 5.0】バックエンドもAstroで完結!?Astro DB × Actions で作る「型安全」なフルスタック開発 🛡️🔥

Posted at

こんにちは😊
株式会社プロドウガ@YushiYamamotoです!
らくらくサイトの開発・運営を担当しながら、React.js・Next.js専門のフリーランスエンジニアとしても活動しています❗️

これまでの記事で、Astroが「静的サイト」や「UI/UX」において最強クラスであることをお伝えしてきました。
しかし、みなさんの中にはこんな疑問があるかもしれません。

「でも、データベースを使うような動的アプリは Next.js 一択なんでしょ?」

……いいえ、そんなことはありません!
2025年の今、Astroは Astro DBAstro Actions という2つの強力な武器を手に入れ、「フルスタックフレームワーク」 へと進化を遂げました。

今日は、APIルートを一切書かずに、データベースへの読み書きを行う「ゲストブックアプリ」を作りながら、その開発体験の良さを紹介します。


1. Astro DB とは? 🗄️

Astro DBは、Astroのために設計されたSQLデータベースです(中身はLibSQL/SQLite)。
最大の特徴は、**「TypeScriptの設定ファイルでスキーマ(テーブル構造)を定義できる」**こと。

SQL文(CREATE TABLE...)を書く必要はありません。astro.config.mjs の隣に設定ファイルを書くだけで、型安全なDB操作が可能になります。

🛠️ セットアップ

npx astro add db

📝 テーブル定義 (db/config.ts)

import { defineDb, defineTable, column } from 'astro:db';

// コメントテーブルの定義
const Comment = defineTable({
  columns: {
    id: column.number({ primaryKey: true }),
    author: column.text(),
    body: column.text(),
    likes: column.number({ default: 0 }),
    publishedAt: column.date({ default: new Date() }),
  }
});

export default defineDb({
  tables: { Comment },
});

これだけで、自動的に型定義が生成されます。
db.select().from(Comment) と書けば、戻り値の型は自動的に推論されます。最高ですね。


2. Astro Actions (Server Actions) とは? ⚡️

従来、フロントエンドからDBを操作するには、/api/comments のようなAPIエンドポイントを作り、fetch でデータを送信する必要がありました。
これには「型が合わない」「バリデーションが面倒」という問題がつきまといます。

Astro Actions は、バックエンドの関数を直接クライアントから呼び出せる仕組みです(RPC)。
Zod によるバリデーションが標準搭載されており、不正なデータはサーバー側の処理に入る前に弾かれます。


3. 実践:ゲストブックを作ってみよう 📖

それでは、実際に「コメント投稿機能」を作ってみましょう。

Step 1: Actionの定義 (src/actions/index.ts)

サーバー側で実行されるロジックを定義します。

actions/index.ts のコード
import { defineAction } from 'astro:actions';
import { z } from 'astro:schema';
import { db, Comment } from 'astro:db';

export const server = {
  // コメント投稿アクション
  postComment: defineAction({
    // 1. 入力値のバリデーション (Zod)
    input: z.object({
      author: z.string().min(1, "名前を入力してください").max(20, "名前が長すぎます"),
      body: z.string().min(1, "コメントを入力してください").max(140, "140文字以内で入力してください"),
    }),
    
    // 2. サーバー処理 (ハンドラー)
    handler: async (input) => {
      // DBに挿入
      await db.insert(Comment).values({
        author: input.author,
        body: input.body,
      });

      // 成功メッセージを返す
      return { success: true, message: "コメントを投稿しました!🎉" };
    },
  }),
};

Step 2: フロントエンドの実装 (src/pages/index.astro)

Astroコンポーネントから、定義したActionを呼び出します。
JavaScriptを使わない「プログレッシブエンハンスメント」なフォームとしても動作しますが、今回は client:load を使ったReactコンポーネントから呼び出す例を紹介します。

Reactフォームコンポーネント (components/CommentForm.tsx)

CommentForm.tsx のコード
import { actions } from 'astro:actions';
import { useState } from 'react';

export const CommentForm = () => {
  const [status, setStatus] = useState<string>('');

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    
    // ✨ ここが魔法!APIエンドポイントではなく関数として呼び出す
    const { data, error } = await actions.postComment(formData);

    if (error) {
      // Zodのバリデーションエラーなどがここに入ります
      setStatus(`エラー: ${error.message}`);
      return;
    }

    setStatus(data.message);
    // フォームのリセットやリロード処理など
    window.location.reload(); 
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4 bg-gray-50 p-6 rounded-lg">
      <div>
        <label className="block text-sm font-bold">お名前</label>
        <input name="author" className="border p-2 w-full rounded" />
      </div>
      <div>
        <label className="block text-sm font-bold">コメント</label>
        <textarea name="body" className="border p-2 w-full rounded" />
      </div>
      <button type="submit" className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
        送信する 🚀
      </button>
      {status && <p className="text-center font-bold text-green-600">{status}</p>}
    </form>
  );
};

ページ本体 (pages/index.astro)

---
import { db, Comment, desc } from 'astro:db';
import { CommentForm } from '../components/CommentForm';

// サーバーサイドでデータを取得 (ビルド時 or SSR時)
const comments = await db.select().from(Comment).orderBy(desc(Comment.publishedAt));
---

<main class="max-w-2xl mx-auto p-10">
  <h1 class="text-3xl font-bold mb-8">Astro Guestbook 📖</h1>
  
  <!-- 投稿フォーム (React Island) -->
  <CommentForm client:load />

  <!-- コメント一覧 (静的HTMLとしてレンダリング) -->
  <div class="mt-10 space-y-4">
    {comments.map((c) => (
      <div class="border-b pb-4">
        <div class="flex justify-between items-center mb-1">
          <span class="font-bold text-lg">{c.author}</span>
          <span class="text-sm text-gray-500">{c.publishedAt.toLocaleDateString()}</span>
        </div>
        <p class="text-gray-700">{c.body}</p>
      </div>
    ))}
  </div>
</main>

4. なぜ Astro DB × Actions なのか? 🤔

Next.jsのServer Actionsも素晴らしいですが、Astroの組み合わせには独自のメリットがあります。

  1. 設定ゼロのDB環境: astro add db だけでローカルDBが立ち上がり、シードデータの投入も簡単です。Dockerすら不要です。
  2. 完全な型安全性: DBスキーマからActionの入力、クライアントの呼び出しまで、TypeScriptの型が途切れることがありません。
  3. ポータビリティ: Astro DBはローカルではSQLiteファイルですが、本番環境(Astro StudioやLibSQL互換プロバイダ)へ簡単にデプロイできます。

SSRモードへの切り替え
動的なアプリケーションを作る場合、astro.config.mjsoutput: 'server' (または 'hybrid') を設定するのを忘れないでください。これにより、リクエストごとに最新のDBデータを取得できます。


5. まとめ:Astroは「Webの万能ナイフ」になった 🇨🇭

5日間にわたり、Astroの魅力をお伝えしてきました。

  1. 爆速の静的サイト (SSG)
  2. 必要な時だけ動くJS (Islands Architecture)
  3. アプリのような遷移 (View Transitions)
  4. 型安全なバックエンド (Astro DB / Actions)

これら全てを1つのフレームワークで、しかも驚くほど低い学習コストで実現できるのがAstroの凄さです。
「メディアサイトはAstro、アプリはNext.js」という境界線は、今まさに溶けつつあります。

ぜひ皆さんも、次のプロジェクトではAstroを使って、**「速くて、軽くて、開発しやすい」**最高のWeb体験を作ってみてください!🚀

5日間お付き合いいただき、ありがとうございました!


最後に:業務委託のご相談を承ります

私は業務委託エンジニアとしてWEB制作やシステム開発を請け負っています。最新技術を活用したレスポンシブなWebサイト制作、インタラクティブなアプリケーション開発、API連携など幅広いご要望に対応可能です。

「課題解決に向けた即戦力が欲しい」「高品質なWeb制作を依頼したい」という方は、お気軽にご相談ください。一緒にビジネスの成長を目指しましょう!

👉 ポートフォリオ

🌳 らくらくサイト

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?