API 端点

Astro 允许你创建自定义的 API 端点(以下简称为“端点”)来提供任何类型的数据。你可以使用它来生成图像、公开 RSS 文档或将它们用作 API 路由来为你的站点构建完整的 API。

在静态生成的站点中,你的自定义端点在构建时被调用以生成静态文件。如果你选择启用 SSR 模式,自定义端点会变成根据请求调用的实时服务器端点。静态和 SSR 端点的定义类似,但 SSR 端点支持附加额外的功能。

要想要创建自定义端点,请将 .js.ts 文件添加到 /pages 目录。.js.ts 这样的扩展名将在构建过程中被删除,因此文件名中应包含你要创建的数据的扩展名。例如,文件 src/pages/data.json.ts 将被构建为 API 端点 /data.json

端点导出一个 get 函数(可选的以 async 导出),它的唯一参数是一个对象,其具有两个属性(paramsrequest)。它返回一个带有 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。例如,生成一个二进制的:

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 类型来约束 API 端点函数:

import type { APIRoute } from 'astro';

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

端点同页面一样,都支持 Astro 的动态路由功能。使用中括号包裹参数作为文件名,并导出 getStaticPaths() 函数。然后,你可以使用传递给 API 端点函数(get)的 params 属性访问参数:

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

const usernames = ["张三", "李四", "王五"]

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"} },
    ]
}

这将在构建时生成三个 JSON 端点:/api/1.json/api/2.json/api/3.json。带端点的动态路由与页面的工作方式相同,但由于端点是一个函数而不是组件,因此 props 是不被支持的。

就像上面提到的,端点函数的唯一参数是一个对象,此对象包含一个名为 request 的属性;但在静态模式下,你只能使用 request.url。这个属性将返回当前端点的完整 URL,并且与 Astro.request.url 对页面的作用相同。

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 模式下使用:文件可以导出一个名为 get 的函数,该函数接一个收具有 paramsrequest 属性的对象。

但是,不同于静态模式,当你使用 SSR 模式时,端点将在请求时被构建。这解锁了在构建时不可用的新特性,并允许你构建 API 路由以监听请求,并在服务器上安全地执行代码。

服务器端点可以在不导出 getStaticPaths 的情况下访问 params。服务器端点可以返回一个 Response 对象,其允许你为返回的数据设置状态码(statusheaders

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.jsonparams.id 将被设置为 helmet。如果我们模拟的数据库中存在 helmet,端点将使用它创建一个包含 product 数据(格式化为 JSON)的 Response 对象,并返回成功的 HTTP状态码。如果没有,它将使用一个 Response 对象返回 404 响应。

除了 get 函数,您还可以使用任何 HTTP 方法 作为名称导出函数。当有请求进来时,Astro 会检查该方法并调用相应的函数。

你还可以导出 all 函数来兜底,它会匹配没有相应导出函数匹配的任何 HTTP 方法。如果有没有匹配到的请求,并且没有使用 all 函数进行兜底,Astro 将会将这些请求重定向到你站点的 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: "这是一个 POST 请求!"
    })
  }
}

export const del: APIRoute = ({ request }) => {
  return {
    body: JSON.stringify({
      message: "这是一个 DELETE 请求!"
    })
  }
}

export const all: APIRoute = ({ request }) => {
  return {
    body: JSON.stringify({
      message: `这是一个 ${request.method} 请求!`
    })
  }
}

在 SSR 模式下,request 属性返回一个可用的 Request 对象,该对象引用当前请求。这允许你接收数据并检查 headers

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 });
}

由于 API 路由中不存在 Astro.redirect,你可以使用 Response.redirect 来进行重定向:

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

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

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

  return Response.redirect(link, 307);
}

例子:对验证码进行验证

Section titled 例子:对验证码进行验证

服务器端点可用作 REST API 端点来运行身份验证、数据库访问和验证等功能,而不会将敏感数据暴露给客户端。

在下面的示例中,API 路由将在不会将机密暴露给客户端的前提下,验证 Google reCAPTCHA v3。

在服务器上,我们定义了一个 post 方法来接收验证码数据,然后使用 reCAPTCHA 的 API 进行验证。在这里,我们可以安全地定义机密值或读取环境变量。

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          // 从客户端传入的令牌(token)
  };

  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 方法访问我们的 API 端点:

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">你是人类吗?</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) {
            // 验证成功
          } else {
            // 验证失败
          }
        })
      }
    </script>
  </body>
</html>