エンドポイント

Astroでは、あらゆる種類のデータを提供するためのカスタムエンドポイントを作成できます。これを利用して、画像を生成したり、RSSを公開したり、またはAPIルーティングとして使用してサイトの完全なAPIを構築できます。

静的に生成されたサイトでは、カスタムエンドポイントは静的ファイルを生成するため、ビルド時に呼び出されます。SSRモードを選択した場合、カスタムエンドポイントはリクエストに応じて呼び出されるライブサーバーエンドポイントに変わります。静的エンドポイントとSSRエンドポイントは同じ様に定義されますが、SSRエンドポイントは追加機能をサポートします。

静的ファイルのエンドポイント

Section titled 静的ファイルのエンドポイント

カスタムエンドポイントを作成するには、.jsまたは.tsファイルを/pagesディレクトリに追加してください。.jsまたは.tsの拡張子はビルドプロセス中に削除されるので、ファイル名には作成したいデータの拡張子を含める必要があります。たとえば、src/pages/data.json.tsは、/data.jsonエンドポイントを構築します。

エンドポイントは、Astroグローバルと同様のプロパティを持つコンテキストオブジェクト (EN)を受け取るget関数(オプションでasync)をエクスポートします。これはbodyを持つオブジェクトを返し、Astroはビルド時にこれを呼び出し、bodyの内容を使ってファイルを生成します。

src/pages/builtwith.json.ts
// 出力: /builtwith.json
export async function get({params, request}) {
  return {
    body: JSON.stringify({
      name: 'Astro',
      url: 'https://astro.build/',
    }),
  };
}

戻り値のオブジェクトは、encodingプロパティを持つことができます。これは、Node.jsのfs.writeFileメソッドで受け入れられる有効なBufferEncodingであれば何でもかまいません。たとえば、バイナリのpngイメージを生成する場合は次のようになります。

src/pages/astro-logo.png.ts
export async function get({ params, request }) {
  const response = await fetch("https://astro.build/assets/press/full-logo-light.png");
  const buffer = Buffer.from(await response.arrayBuffer());
  return {
    body: buffer,
    encoding: 'binary',
  };
}

また、APIRoute型を使用してエンドポイント関数に型付けもできます。

import type { APIRoute } from 'astro';

export const get: APIRoute = async function get ({params, request}) {
...

paramsと動的ルーティング

Section titled paramsと動的ルーティング

エンドポイントは、ページと同じ動的ルーティング機能をサポートしています。ファイル名を括弧付きのパラメーター名とし、getStaticPaths()関数 (EN)をエクスポートしてください。そして、エンドポイント関数に渡されたparamsプロパティを使用して、パラメーターにアクセスします。

src/pages/[id].json.ts
import type { APIRoute } from 'astro';

const usernames = ["Sarah", "Chris", "Dan"]

export const get: APIRoute = ({ params, request }) => {
  const id = params.id;
  return {
    body: JSON.stringify({
      name: usernames[id]
    })
  }
};

export function getStaticPaths () {
  return [ 
    { params: { id: "0"} },
    { params: { id: "1"} },
    { params: { id: "2"} },
  ]
}

これにより、ビルド時に/api/1.json/api/2.json/api/3.jsonという3つのJSONエンドポイントが生成されます。エンドポイントによる動的ルーティングはページと同じように動作しますが、エンドポイントはコンポーネントではなく関数であるため、props (EN)はサポートされていません。

すべてのエンドポイントはrequestプロパティを受け取りますが、静的モードではrequest.urlにのみアクセスが可能です。これは、現在のエンドポイントの完全なURLを返し、Astro.request.url (EN)がページに対して行うのと同じように動作します。

src/pages/request-path.json.ts
import type { APIRoute } from 'astro';

export const get: APIRoute = ({ params, request }) => {
  return {
    body: JSON.stringify({
      path: new URL(request.url).pathname
    })
  };
}

サーバーエンドポイント(APIルーティング)

Section titled サーバーエンドポイント(APIルーティング)

静的ファイルエンドポイントのセクションで説明したものはすべて、SSRモードでも使用できます。ファイルは、Astroグローバルと同様のプロパティを持つコンテキストオブジェクト (EN)を受け取るget関数をエクスポートできます。

しかし、staticモードとは異なり、serverモードを設定すると、エンドポイントはリクエストされた時点で構築されます。これにより、ビルド時には利用できない新しい機能がアンロックされ、リクエストをリッスンするAPIルートを構築したり、実行時にサーバー上で安全にコードを実行できるようになります。

サーバーエンドポイントは、getStaticPathsをエクスポートせずparamsにアクセスでき、Responseオブジェクトを返せるので、ステータスコードやヘッダーを設定できます。

src/pages/[id].json.js
import { getProduct } from '../db';

export async function get({ params }) {
  const id = params.id;
  const product = await getProduct(id);

  if (!product) {
    return new Response(null, {
      status: 404,
      statusText: 'Not found'
    });
  }

  return new Response(JSON.stringify(product), {
    status: 200,
    headers: {
      "Content-Type": "application/json"
    }
  });
}

これは、動的ルーティングにマッチするすべてのリクエストに応答します。たとえば、/helmet.jsonに移動した場合、params.idhelmetに設定されます。モックの商品データベースにhelmetが存在すれば、エンドポイントはResponseオブジェクトを作成してJSONで応答し、HTTPステータスコードとして成功を返します。存在しない場合は、Responseオブジェクトを使用して404で応答します。

get関数に加え、任意のHTTPメソッド名を持つ関数をエクスポートできます。リクエストが来ると、Astroはそのメソッドをチェックして、対応する関数を呼び出します。

また、対応するエクスポートされた関数がないメソッドにマッチするように、all関数をエクスポートできます。一致するメソッドがないリクエストの場合、サイトの404ページにリダイレクトされます。

src/pages/methods.json.ts
export const get: APIRoute = ({ params, request }) => {
  return {
    body: JSON.stringify({
      message: "This was a GET!"
    })
  }
};

export const post: APIRoute = ({ request }) => {
  return {
    body: JSON.stringify({
      message: "This was a POST!"
    })
  }
}

export const del: APIRoute = ({ request }) => {
  return {
    body: JSON.stringify({
      message: "This was a DELETE!"
    })
  }
}

export const all: APIRoute = ({ request }) => {
  return {
    body: JSON.stringify({
      message: `This was a ${request.method}!`
    })
  }
}

SSRモードでは、requestプロパティは、現在のリクエストを参照する完全に使用可能なRequestオブジェクトを返します。これにより、データの受け入れやヘッダーのチェックができます。

src/pages/test-post.json.ts
export const post: APIRoute = async ({ request }) => {
  if (request.headers.get("Content-Type") === "application/json") {
    const body = await request.json();
    const name = body.name;
    return new Response(JSON.stringify({
      message: "名前: " + name
    }), {
      status: 200
    })
  }
  return new Response(null, { status: 400 });
}

エンドポイントコンテキストは、Astro.redirectに似たredirect()ユーティリティをエクスポートします。

src/pages/links/[id].js
import { getLinkUrl } from '../db';

export async function get({ params, redirect }) {
  const { id } = params;
  const link = await getLinkUrl(id);

  if (!link) {
    return new Response(null, {
      status: 404,
      statusText: 'Not found'
    });
  }

  return redirect(link, 307);
}

例:CAPTCHA(キャプチャ)の検証

Section titled 例:CAPTCHA(キャプチャ)の検証

サーバーエンドポイントをREST APIのエンドポイントとして使用することで、機密データをクライアントに公開することなく、認証、データベースアクセス、検証などの機能を実行できます。

以下の例では、Google reCAPTCHA v3を検証するためにAPIルートを使用していますが、これはクライアントにシークレットを公開しません。

サーバー上では、recaptchaのデータを受け入れるpostメソッドを定義し、reCAPTCHAのAPIを使って検証を行います。ここで、secret値を安全に定義したり、環境変数を読み込んだりできます。

src/pages/recaptcha.js
export async function post({ request }) {
  const data = await request.json();

  const recaptchaURL = 'https://www.google.com/recaptcha/api/siteverify';
  const requestBody = {
    secret: "YOUR_SITE_SECRET_KEY",   // 環境変数にできます
    response: data.recaptcha          // クライアントから渡されたトークン
  };

  const response = await fetch(recaptchaURL, {
    method: "POST",
    body: JSON.stringify(requestBody)
  });

  const responseData = await response.json();

  return new Response(JSON.stringify(responseData), { status: 200 });
}

そして、クライアントスクリプトからfetchを使ってエンドポイントにアクセスします。

src/pages/index.astro
<html>
  <head>
    <script src="https://www.google.com/recaptcha/api.js"></script>
  </head>

  <body>
    <button class="g-recaptcha" 
      data-sitekey="PUBLIC_SITE_KEY" 
      data-callback="onSubmit" 
      data-action="submit">CAPTCHA認証のためクリックしてください</button>

    <script is:inline>
      function onSubmit(token) {
        fetch("/recaptcha", {
          method: "POST",
          body: JSON.stringify({ recaptcha: token })
        })
        .then((response) => response.json())
        .then((gResponse) => {
          if (gResponse.success) {
            // CAPTCHA認証が成功した場合
          } else {
            // CAPTCHA認証が失敗
          }
        })
      }
    </script>
  </body>
</html>