【Astro Actions入門】いいねボタンを実装する完全ガイド|手順・コード例付き

はじめに

こんにちは。 株式会社メンバーズのフロントエンドエンジニアの髙橋です。現在、モダンフロントエンド開発の学習の一環として、Astro の学習をしています。
今回は、Astro 4.15 から追加された Astro Actions の理解を深めるために、「いいね」機能の実装を行いました。

Astro Actions は、バックエンドの関数をクライアントから型安全に呼び出せる便利な機能です。従来のfetchによるAPIリクエストでは型情報が失われるため、バックエンド関数の型の安全性が確保できません。しかし、Astro Actions を使うことで、型情報を保持したままバックエンドの関数を呼び出すことができます。

この記事では、Astro Actions を使った、いいねボタンの実装手順を紹介します。
以下のようなクリックごとにいいね数が+1されるボタンを実装します。

Astro Actions とは?機能・メリットを解説

基本的な使い方

まずは、Astro Actions の基本的な使い方を説明します。

バックエンド側でのアクションの定義

Astro Actions を使うには、src/actions/index.ts にアクションを定義する必要があります。 以下のように定義します。

import { defineAction } from 'astro:actions'
import { z } from 'astro:schema'

export const server = {
  getLike: defineAction({ // アクション名の定義 (このコードでは getLike)
    input: z.object({
      blogId: z.string() // Zod スキーマでのアクションの引数を定義 (このコードでは blogId)
    }),
    handler: async ({ blogId }) => { // アクションで実行される関数を定義
      // 処理の内容
    }
  })
}

クライアントからの呼び出し方

クライアントからは、astro:actions からインポートした actions オブジェクトを使います。 以下は、クライアントからアクションを呼び出す例です。

---
---
<button id="like-button" data-blog-id={blogId}>いいね</button>

<script>
  import { actions } from 'astro:actions'

  actions.getLike({ blogId })

【実践ステップ】Astro Actionsを使った『いいね』

ボタンの実装手順

ここから、いいねボタンの実装手順を説明します。

1. アクションの定義

まずは、いいね数を表示するための getLike アクションと、いいね数を増やすための addLike アクションを定義します。

src/actions/index.ts に以下のように定義します。

index.ts

import { addLike } from './addLike'
import { getLike } from './getLike'

export const server = { // ファイルを分けて定義することも可能
  addLike,
  getLike
}

getLike.ts の例

export const getLike = defineAction({
  input: z.object({
    blogId: z.string()
  }),
  handler: async ({ blogId }) => {
    // ID が blogId であるデータソースからいいね数を取得しそれを返す関数
    const blogResponse = await client.get({contentId: blogId}) // データソースに合わせて実装を変更してください
    const likeCount = blogResponse.like_count ?? 0
    return { likeCount }
  }
})

addLike.ts の例

export const addLike = defineAction({
  input: z.object({
    blogId: z.string()
  }),
  handler: async ({ blogId }) => {
    // ID が blogId であるデータソースからいいね数を取得
    // いいね数を +1 し、blogId であるデータソースを更新
    // 新しいいいね数を返す関数
    const blogResponse = await client.get({contentId: blogId}) // データソースに合わせて実装を変更してください
    const newLikeCount = blogResponse.like_count + 1
    await client.update({ // データソースに合わせて実装を変更してください
        contentId: blogId,
        content: {
          like_count: newLikeCount
        }
      })
    return { likeCount: newLikeCount }
  }
})

2. クライアントコンポーネントの作成

次に、クライアント側のコンポーネントを作成します。

 ---
/*
いいねボタンコンポーネント

上位コンポーネントから blogId を受け取る
blogId に対応するいいね数を Astro Actions で取得し、ボタンのテキストに表示
button がクリックされたら Astro Actions の addLike メソッドを呼び出し、いいね数を増やす
*/
interface Props {
  blogId: string
}

const { blogId } = Astro.props 
---

<button id="like-button" data-blog-id={blogId}>いいね: 読込中</button>

<script>
  import { actions } from 'astro:actions'

  const button = document.getElementById('like-button')
  const blogId = button.dataset.blogId

  // 記事のいいね数を取得してボタンに反映
  actions
    .getLike({ blogId })
    .then((like) => {
      button.innerText = `いいね: ${like.likeCount}`
      // ボタンのイベントリスナーにいいね追加処理を登録
      button.addEventListener('click', async () => {
        const beforeInnerText = button.innerText
        button.innerText = 'いいね中...'
        const addLikeRes = await actions.addLike({ blogId })
        button.innerText = `いいね: ${addLikeRes.likeCount}`
      })
    })
</script>

3. いいねボタンの完成

ここまで実装すると、以下のように動作するいいねボタンが完成します。初期表示時は「いいね: 読込中」と表示され、データの取得後に現在のいいね数が表示されます。ボタンをクリックすると「いいね中...」という状態表示に切り替わり、処理が完了すると、更新されたいいね数が表示されます。

このコンポーネントはどのブログ記事ページにも簡単に組み込むことができ、blogIdを渡すだけで動作します。また、型安全性が確保されているため、開発時のミスを防ぎやすくなっています。

【実装のポイント】Astro Actionsの効果的な活用法

型安全性の確保

型安全性を確保するために、Zod のスキーマ定義を使って入力値のバリデーションを行います。Zod とは、TypeScript 向けのスキーマバリデーションライブラリで、スキーマ宣言とデータ検証を行うためのライブラリです。Astro Actions では、この Zod を用いて型安全な API を実現しています。
defineAction の input プロパティに Zod のスキーマを指定することで、入力値の型情報を設定できます。 スキーマの定義の詳細は、Zod のドキュメントOpen in new tabを参照してください。

export const addLike = defineAction({
  // 入力値のバリデーション
  input: z.object({
    blogId: z.string()
  }),
  handler: async ({ blogId }) => {
    // 処理
  }
})

【トラブル回避】Astro Actionsのエラーハンドリング解説

アクションのバックエンド側でのエラーハンドリングには、ActionError を使います。 (astro:actions からインポートします) ActionError には、404 や 500 などのエラーコードと、メッセージを設定できます。 ステータスコードを設定できるので、開発時や運用時にエラーのデバッグがしやすくなります。

export const addLike = defineAction({
  input: z.object({
    blogId: z.string()
  }),
  handler: async ({ blogId }) => {
    try {
      // 処理の内容
    } catch (error) {
      throw new ActionError({
        code: 'INTERNAL_SERVER_ERROR',
        message: `内部エラー: ${error}`
      })
    }
  }
})

【注意点】セキュリティと最適化のポイント

アクションのエンドポイントは外部に公開されるため、セキュリティ上のリスクに注意が必要です。
具体的なリスクとして

  • 認証されていないユーザによる不正アクセス
  • DoS 攻撃
  • データ漏えい

などが挙げられます。
これらのリスクを軽減するために、入力値の検証、適切な認証処理や、レート制限などを入れる必要があります。

まとめ

以上が、Astro Actions を使ったいいねボタンの実装手順です。 Astro Actions を使えば、バックエンドの関数を簡単にクライアントから型安全に呼び出すことができます。

実装してみた感想としては Astro Actions を使うことで、フロントエンドとバックエンドの連携がシームレスになり、開発効率が向上しました。従来の RESTful API などを使用する場合と比較して

  • 型情報が自動的に連携されるため、型の不一致によるバグが減少
  • クライアント側のコードがシンプルになり、メンテナンス性が向上
  • バックエンドの処理をフロントエンドから直接呼び出せるため、開発の流れが自然

などのメリットを感じました。
また、Astro Actions を応用すると、今回のいいね機能以外にも、フォームの送信、コメント投稿、その他の外部サービス連携が実装しやすくなると感じました。

参考資料

この記事が役に立ったと思ったら、
ぜひ「いいね」とシェアをお願いします!

リンクをコピーXでシェアするfacebookでシェアする

この記事を書いた人

髙橋 穂高
髙橋 穂高
2018年にメンバーズに新卒で入社。現在は Astro を使った Jamstack 構成の開発に携わっています。
詳しく見る
ページトップへ戻る