Skip to content

操作

Added in: astro@4.15

Astro Actions 允许你定义和调用具有类型安全性的后端函数。操作为你执行数据提取、JSON 解析和输入验证。与使用 API 端点 相比,这可以大大减少所需的样板量。

¥Astro Actions allow you to define and call backend functions with type-safety. Actions perform data fetching, JSON parsing, and input validation for you. This can greatly reduce the amount of boilerplate needed compared to using an API endpoint.

使用操作而不是 API 端点,实现客户端和服务器代码之间的无缝通信,并:

¥Use actions instead of API endpoints for seamless communication between your client and server code and to:

  • 使用 Zod 验证 自动验证 JSON 和表单数据输入。

  • 生成类型安全的函数以从客户端甚至 来自 HTML 表单操作 调用你的后端。无需手动 fetch() 调用。

  • 使用 ActionError 对象标准化后端错误。

¥Basic usage

操作在从 src/actions/index.ts 导出的 server 对象中定义:

¥Actions are defined in a server object exported from src/actions/index.ts:

src/actions/index.ts
import { defineAction } from 'astro:actions';
import { z } from 'astro:schema';
export const server = {
myAction: defineAction({ /* ... */ })
}

你的操作可作为 astro:actions 模块中的函数使用。导入 actions 并在 UI 框架组件表单 POST 请求 中在客户端调用它们,或者在 Astro 组件中使用 <script> 标签。

¥Your actions are available as functions from the astro:actions module. Import actions and call them client-side within a UI framework component, a form POST request, or by using a <script> tag in an Astro component.

调用操作时,它会返回一个对象,其中 data 包含 JSON 序列化结果,或 error 包含抛出的错误。

¥When you call an action, it returns an object with either data containing the JSON-serialized result, or error containing thrown errors.

src/pages/index.astro
---
---
<script>
import { actions } from 'astro:actions';
async () => {
const { data, error } = await actions.myAction({ /* ... */ });
}
</script>

编写你的第一个操作

标题部分 编写你的第一个操作

¥Write your first action

按照以下步骤定义操作并在 Astro 页面中的 script 标记中调用它。

¥Follow these steps to define an action and call it in a script tag in your Astro page.

  1. Create a src/actions/index.ts file and export a server object.

    src/actions/index.ts
    export const server = {
    // action declarations
    }
  2. Import the defineAction() utility from astro:actions, and the z object from astro:schema.

    src/actions/index.ts
    import { defineAction } from 'astro:actions';
    import { z } from 'astro:schema';
    export const server = {
    // action declarations
    }
  3. Use the defineAction() utility to define a getGreeting action. The input property will be used to validate input parameters with a Zod schema and the handler() function includes the backend logic to run on the server.

    src/actions/index.ts
    import { defineAction } from 'astro:actions';
    import { z } from 'astro:schema';
    export const server = {
    getGreeting: defineAction({
    input: z.object({
    name: z.string(),
    }),
    handler: async (input) => {
    return `Hello, ${input.name}!`
    }
    })
    }
  4. Create an Astro component with a button that will fetch a greeting using your getGreeting action when clicked.

    src/pages/index.astro
    ---
    ---
    <button>Get greeting</button>
    <script>
    const button = document.querySelector('button');
    button?.addEventListener('click', async () => {
    // Show alert pop-up with greeting from action
    });
    </script>
  5. To use your action, import actions from astro:actions and then call actions.getGreeting() in the click handler. The name option will be sent to your action’s handler() on the server and, if there are no errors, the result will be available as the data property.

    src/pages/index.astro
    ---
    ---
    <button>Get greeting</button>
    <script>
    import { actions } from 'astro:actions';
    const button = document.querySelector('button');
    button?.addEventListener('click', async () => {
    // Show alert pop-up with greeting from action
    const { data, error } = await actions.getGreeting({ name: "Houston" });
    if (!error) alert(data);
    })
    </script>
See the full Actions API documentation for details on defineAction() and its properties.

¥Organizing actions

项目中的所有操作都必须从 src/actions/index.ts 文件中的 server 对象导出。你可以内联定义操作,也可以将操作定义移动到单独的文件并导入它们。你甚至可以将相关函数分组到嵌套对象中。

¥All actions in your project must be exported from the server object in the src/actions/index.ts file. You can define actions inline or you can move action definitions to separate files and import them. You can even group related functions in nested objects.

例如,要将所有用户操作放在一起,你可以创建一个 src/actions/user.ts 文件并将 getUsercreateUser 的定义嵌套在单个 user 对象中。

¥For example, to colocate all of your user actions, you can create a src/actions/user.ts file and nest the definitions of both getUser and createUser inside a single user object.

src/actions/user.ts
import { defineAction } from 'astro:actions';
export const user = {
getUser: defineAction(/* ... */),
createUser: defineAction(/* ... */),
}

然后,你可以将此 user 对象导入你的 src/actions/index.ts 文件中,并将其作为顶层键添加到 server 对象以及任何其他操作:

¥Then, you can import this user object into your src/actions/index.ts file and add it as a top-level key to the server object alongside any other actions:

src/actions/index.ts
import { user } from './user';
export const server = {
myAction: defineAction({ /* ... */ }),
user,
}

现在,你的所有用户操作都可以从 actions.user 对象调用:

¥Now, all of your user actions are callable from the actions.user object:

  • actions.user.getUser()

  • actions.user.createUser()

¥Handling returned data

操作返回一个对象,其中包含 datahandler() 的类型安全返回值,或包含任何后端错误的 error。错误可能来自 input 属性上的验证错误或 handler() 中抛出的错误。

¥Actions return an object containing either data with the type-safe return value of your handler(), or an error with any backend errors. Errors may come from validation errors on the input property or thrown errors within the handler().

操作返回可处理日期、地图、集合和 URL 使用 Devalue 库 的自定义数据格式。因此,你无法像使用常规 JSON 那样轻松地检查来自网络的响应。对于调试,你可以检查操作返回的 data 对象。

¥Actions return a custom data format that can handle Dates, Maps, Sets, and URLs using the Devalue library. Therefore, you can’t easily inspect the response from the network like you can with regular JSON. For debugging, you can instead inspect the data object returned by actions.

¥Checking for errors

最好在使用 data 属性之前检查是否存在 error。这允许你提前处理错误并确保 data 在没有 undefined 检查的情况下定义。

¥It’s best to check if an error is present before using the data property. This allows you to handle errors in advance and ensures data is defined without an undefined check.

const { data, error } = await actions.example();
if (error) {
// handle error cases
return;
}
// use `data`

直接访问 data 而不进行错误检查

标题部分 直接访问 data 而不进行错误检查

¥Accessing data directly without an error check

要跳过错误处理,例如在原型设计或使用将为你捕获错误的库时,请在操作调用中使用 .orThrow() 属性来抛出错误,而不是返回 error。这将直接返回操作的 data

¥To skip error handling, for example while prototyping or using a library that will catch errors for you, use the .orThrow() property on your action call to throw errors instead of returning an error. This will return the action’s data directly.

此示例调用 likePost() 操作,该操作从操作 handler 返回更新的点赞数作为 number

¥This example calls a likePost() action that returns the updated number of likes as a number from the action handler:

const updatedLikes = await actions.likePost.orThrow({ postId: 'example' });
// ^ type: number

处理操作中的后端错误

标题部分 处理操作中的后端错误

¥Handling backend errors in your action

你可以使用提供的 ActionError 从操作 handler() 中抛出错误,例如当数据库条目丢失时抛出 “未找到”,当用户未登录时抛出 “unauthorized”。与返回 undefined 相比,这有两个主要好处:

¥You can use the provided ActionError to throw an error from your action handler(), such as “not found” when a database entry is missing, or “unauthorized” when a user is not logged in. This has two main benefits over returning undefined:

  • 你可以设置状态代码,如 404 - Not found401 - Unauthorized。通过让你查看每个请求的状态代码,可以改善开发和生产中的错误调试。

  • 在你的应用代码中,所有错误都会传递给操作结果上的 error 对象。这避免了对数据进行 undefined 检查的需要,并允许你根据出现的问题向用户显示有针对性的反馈。

¥Creating an ActionError

要抛出错误,请从 astro:actions 模块导入 ActionError() 类。向其传递一个人类可读的状态 code(例如 "NOT_FOUND""BAD_REQUEST")和一个可选的 message 以提供有关错误的更多信息。

¥To throw an error, import the ActionError() class from the astro:actions module. Pass it a human-readable status code (e.g. "NOT_FOUND" or "BAD_REQUEST"), and an optional message to provide further information about the error.

此示例在用户未登录时从 likePost 操作抛出错误,在检查假设的 “user-session” cookie 进行身份验证后:

¥This example throws an error from a likePost action when a user is not logged in, after checking a hypothetical “user-session” cookie for authentication:

src/actions/index.ts
import { defineAction, ActionError } from "astro:actions";
import { z } from "astro:schema";
export const server = {
likePost: defineAction({
input: z.object({ postId: z.string() }),
handler: async (input, ctx) => {
if (!ctx.cookies.has('user-session')) {
throw new ActionError({
code: "UNAUTHORIZED",
message: "User must be logged in.",
});
}
// Otherwise, like the post
},
}),
};

¥Handling an ActionError

要处理此错误,你可以从应用调用操作并检查是否存在 error 属性。此属性将为 ActionError 类型,并将包含你的 codemessage

¥To handle this error, you can call the action from your application and check whether an error property is present. This property will be of type ActionError and will contain your code and message.

在下面的示例中,LikeButton.tsx 组件在被点击时会调用 likePost() 操作。如果发生身份验证错误,则使用 error.code 属性来确定是否显示登录链接:

¥In the following example, a LikeButton.tsx component calls the likePost() action when clicked. If an authentication error occurs, the error.code attribute is used to determine whether to display a login link:

src/components/LikeButton.tsx
import { actions } from 'astro:actions';
import { useState } from 'preact/hooks';
export function LikeButton({ postId }: { postId: string }) {
const [showLogin, setShowLogin] = useState(false);
return (
<>
{
showLogin && <a href="/signin">Log in to like a post.</a>
}
<button onClick={async () => {
const { data, error } = await actions.likePost({ postId });
if (error?.code === 'UNAUTHORIZED') setShowLogin(true);
// Early return for unexpected errors
else if (error) return;
// update likes
}}>
Like
</button>
</>
)
}

¥Handling client redirects

从客户端调用操作时,你可以与客户端库(如 react-router)集成,或者使用 Astro 的 navigate() 功能 在操作成功时重定向到新页面。

¥When calling actions from the client, you can integrate with a client-side library like react-router, or you can use Astro’s navigate() function to redirect to a new page when an action succeeds.

此示例在 logout 操作成功返回后导航到主页:

¥This example navigates to the homepage after a logout action returns successfully:

src/pages/LogoutButton.tsx
import { actions } from 'astro:actions';
import { navigate } from 'astro:transitions/client';
export function LogoutButton() {
return (
<button onClick={async () => {
const { error } = await actions.logout();
if (!error) navigate('/');
}}>
Logout
</button>
);
}

从操作中接受表单数据

标题部分 从操作中接受表单数据

¥Accepting form data from an action

操作默认接受 JSON 数据。要从 HTML 表单接受表单数据,请在 defineAction() 调用中设置 accept: 'form'

¥Actions accept JSON data by default. To accept form data from an HTML form, set accept: 'form' in your defineAction() call:

src/actions/index.ts
import { defineAction } from 'astro:actions';
import { z } from 'astro:schema';
export const server = {
comment: defineAction({
accept: 'form',
input: z.object(/* ... */),
handler: async (input) => { /* ... */ },
})
}

¥Validating form data

操作将使用每个输入的 name 属性的值作为对象键,将提交的表单数据解析为对象。例如,包含 <input name="search"> 的表单将被解析为像 { search: 'user input' } 这样的对象。你的操作的 input 架构将用于验证此对象。

¥Actions will parse submitted form data to an object, using the value of each input’s name attribute as the object keys. For example, a form containing <input name="search"> will be parsed to an object like { search: 'user input' }. Your action’s input schema will be used to validate this object.

要在操作处理程序中接收原始 FormData 对象而不是解析的对象,请在操作定义中省略 input 属性。

¥To receive the raw FormData object in your action handler instead of a parsed object, omit the input property in your action definition.

以下示例显示经过验证的新闻稿注册表单,该表单接受用户的电子邮件并需要 “服务条款” 同意复选框。

¥The following example shows a validated newsletter registration form that accepts a user’s email and requires a “terms of service” agreement checkbox.

  1. Create an HTML form component with unique name attributes on each input:

    src/components/Newsletter.astro
    <form>
    <label for="email">E-mail</label>
    <input id="email" required type="email" name="email" />
    <label>
    <input required type="checkbox" name="terms">
    I agree to the terms of service
    </label>
    <button>Sign up</button>
    </form>
  2. Define a newsletter action to handle the submitted form. Validate the email field using the z.string().email() validator, and the terms checkbox using z.boolean():

    src/actions/index.ts
    import { defineAction } from 'astro:actions';
    import { z } from 'astro:schema';
    export const server = {
    newsletter: defineAction({
    accept: 'form',
    input: z.object({
    email: z.string().email(),
    terms: z.boolean(),
    }),
    handler: async ({ email, terms }) => { /* ... */ },
    })
    }
    See the input API reference for all available form validators.
  3. Add a <script> to the HTML form to submit the user input. This example overrides the form’s default submit behavior to call actions.newsletter(), and redirects to /confirmation using the navigate() function:

    src/components/Newsletter.astro
    <form>
    7 collapsed lines
    <label for="email">E-mail</label>
    <input id="email" required type="email" name="email" />
    <label>
    <input required type="checkbox" name="terms">
    I agree to the terms of service
    </label>
    <button>Sign up</button>
    </form>
    <script>
    import { actions } from 'astro:actions';
    import { navigate } from 'astro:transitions/client';
    const form = document.querySelector('form');
    form?.addEventListener('submit', async (event) => {
    event.preventDefault();
    const formData = new FormData(form);
    const { error } = await actions.newsletter(formData);
    if (!error) navigate('/confirmation');
    })
    </script>
    See “Call actions from an HTML form action” for an alternative way to submit form data.

¥Displaying form input errors

你可以使用 原生 HTML 表单验证属性(如 requiredtype="email"pattern)在提交之前验证表单输入。对于后端更复杂的 input 验证,你可以使用提供的 isInputError() 实用函数。

¥You can validate form inputs before submission using native HTML form validation attributes like required, type="email", and pattern. For more complex input validation on the backend, you can use the provided isInputError() utility function.

要检索输入错误,请使用 isInputError() 实用程序检查错误是否由无效输入引起。输入错误包含一个 fields 对象,其中包含每个无法验证的输入名称的消息。你可以使用这些消息提示用户更正其提交。

¥To retrieve input errors, use the isInputError() utility to check whether an error was caused by invalid input. Input errors contain a fields object with messages for each input name that failed to validate. You can use these messages to prompt your user to correct their submission.

以下示例使用 isInputError() 检查错误,然后检查错误是否在电子邮件字段中,最后根据错误创建消息。你可以使用 JavaScript DOM 操作或你喜欢的 UI 框架向用户显示此消息。

¥The following example checks the error with isInputError(), then checks whether the error is in the email field, before finally creating a message from the errors. You can use JavaScript DOM manipulation or your preferred UI framework to display this message to users.

import { actions, isInputError } from 'astro:actions';
const form = document.querySelector('form');
const formData = new FormData(form);
const { error } = await actions.newsletter(formData);
if (isInputError(error)) {
// Handle input errors.
if (error.fields.email) {
const message = error.fields.email.join(', ');
}
}

从 HTML 表单操作调用操作

标题部分 从 HTML 表单操作调用操作

¥Call actions from an HTML form action

你可以使用任何 <form> 元素上的标准属性启用零 JS 表单提交。没有客户端 JavaScript 的表单提交可能既可以作为 JavaScript 加载失败时的后备,也可以作为你更喜欢完全从服务器处理表单时的后备。

¥You can enable zero-JS form submissions with standard attributes on any <form> element. Form submissions without client-side JavaScript may be useful both as a fallback for when JavaScript fails to load, or if you prefer to handle forms entirely from the server.

在服务器上调用 Astro.getActionResult() 会返回表单提交的结果(dataerror),并可用于动态重定向、处理表单错误、更新 UI 等。

¥Calling Astro.getActionResult() on the server returns the result of your form submission (data or error), and can be used to dynamically redirect, handle form errors, update the UI, and more.

要从 HTML 表单调用操作,请将 method="POST" 添加到 <form>,然后使用你的操作设置表单的 action 属性,例如 action={actions.logout}。这将设置 action 属性以使用由服务器自动处理的查询字符串。

¥To call an action from an HTML form, add method="POST" to your <form>, then set the form’s action attribute using your action, for example action={actions.logout}. This will set the action attribute to use a query string that is handled by the server automatically.

例如,此 Astro 组件在单击按钮时调用 logout 操作并重新加载当前页面:

¥For example, this Astro component calls the logout action when the button is clicked and reloads the current page:

src/components/LogoutButton.astro
---
import { actions } from 'astro:actions';
---
<form method="POST" action={actions.logout}>
<button>Log out</button>
</form>

¥Redirect on action success

如果你需要在成功时重定向到新路由,则可以使用服务器上的操作结果。一个常见示例是创建产品记录并重定向到新产品的页面,例如 /products/[id]

¥If you need to redirect to a new route on success, you can use an action’s result on the server. A common example is creating a product record and redirecting to the new product’s page, e.g. /products/[id].

例如,假设你有一个 createProduct 操作,该操作返回生成的产品 ID:

¥For example, say you have a createProduct action that returns the generated product id:

src/actions/index.ts
import { defineAction } from 'astro:actions';
import { z } from 'astro:schema';
export const server = {
createProduct: defineAction({
accept: 'form',
input: z.object({ /* ... */ }),
handler: async (input) => {
const product = await persistToDatabase(input);
return { id: product.id };
},
})
}

你可以通过调用 Astro.getActionResult() 从 Astro 组件中检索操作结果。当调用操作时,这将返回一个包含 dataerror 属性的对象,如果在此请求期间未调用该操作,则返回 undefined

¥You can retrieve the action result from your Astro component by calling Astro.getActionResult(). This returns an object containing data or error properties when an action is called, or undefined if the action was not called during this request.

使用 data 属性构造与 Astro.redirect() 一起使用的 URL:

¥Use the data property to construct a URL to use with Astro.redirect():

src/pages/products/create.astro
---
import { actions } from 'astro:actions';
const result = Astro.getActionResult(actions.createProduct);
if (result && !result.error) {
return Astro.redirect(`/products/${result.data.id}`);
}
---
<form method="POST" action={actions.createProduct}>
<!--...-->
</form>

¥Handle form action errors

在包含表单的 Astro 组件中调用 Astro.getActionResult() 可让你访问 dataerror 对象以进行自定义错误处理。

¥Calling Astro.getActionResult() in the Astro component containing your form gives you access to the data and error objects for custom error handling.

以下示例在 newsletter 操作失败时显示一般失败消息:

¥The following example displays a general failure message when a newsletter action fails:

src/pages/index.astro
---
import { actions } from 'astro:actions';
const result = Astro.getActionResult(actions.newsletter);
---
{result?.error && (
<p class="error">Unable to sign up. Please try again later.</p>
)}
<form method="POST" action={actions.newsletter}>
<label>
E-mail
<input required type="email" name="email" />
</label>
<button>Sign up</button>
</form>

如需更多自定义,你可以使用 使用 isInputError() 实用程序 检查错误是否由无效输入引起。

¥For more customization, you can use the isInputError() utility to check whether an error is caused by invalid input.

以下示例在提交无效电子邮件时在 email 输入字段下渲染错误横幅:

¥The following example renders an error banner under the email input field when an invalid email is submitted:

src/pages/index.astro
---
import { actions, isInputError } from 'astro:actions';
const result = Astro.getActionResult(actions.newsletter);
const inputErrors = isInputError(result?.error) ? result.error.fields : {};
---
<form method="POST" action={'/confirmation' + actions.newsletter}>
<label>
E-mail
<input required type="email" name="email" aria-describedby="error" />
</label>
{inputErrors.email && <p id="error">{inputErrors.email.join(',')}</p>}
<button>Sign up</button>
</form>

发生错误时保留输入值

标题部分 发生错误时保留输入值

¥Preserve input values on error

每当提交表单时,输入都会被清除。要保留输入值,你可以在页面上 启用视图转换 并将 transition:persist 指令应用于每个输入:

¥Inputs will be cleared whenever a form is submitted. To persist input values, you can enable view transitions on the page and apply the transition:persist directive to each input:

<input transition:persist required type="email" name="email" />

使用表单操作结果更新 UI

标题部分 使用表单操作结果更新 UI

¥Update the UI with a form action result

要使用操作的返回值在成功时向用户显示通知,请将操作传递给 Astro.getActionResult()。使用返回的 data 属性来渲染你想要显示的 UI。

¥To use an action’s return value to display a notification to the user on success, pass the action to Astro.getActionResult(). Use the returned data property to render the UI you want to display.

此示例使用 addToCart 操作返回的 productName 属性显示成功消息。

¥This example uses the productName property returned by an addToCart action to show a success message.

src/pages/products/[slug].astro
---
import { actions } from 'astro:actions';
const result = Astro.getActionResult(actions.addToCart);
---
{result && !result.error && (
<p class="success">Added {result.data.productName} to cart</p>
)}
<!--...-->

高级:通过会话保留操作结果

标题部分 高级:通过会话保留操作结果

¥Advanced: Persist action results with a session

Added in: astro@5.0.0

操作结果显示为 POST 提交。这意味着当用户关闭并重新访问页面时,结果将重置为 undefined。如果用户尝试刷新页面,他们还将看到 “确认表单重新提交?” 对话框。

¥Action results are displayed as a POST submission. This means that the result will be reset to undefined when a user closes and revisits the page. The user will also see a “confirm form resubmission?” dialog if they attempt to refresh the page.

要自定义此行为,你可以添加中间件来手动处理操作的结果。你可以选择使用 cookie 或会话存储来持久保存操作结果。

¥To customize this behavior, you can add middleware to handle the result of the action manually. You may choose to persist the action result using a cookie or session storage.

astro:actions 开始,从 creating a middleware file and importing the getActionContext() utility 开始。此函数返回一个 action 对象,其中包含有关传入操作请求的信息,包括操作处理程序以及该操作是否从 HTML 表单调用。getActionContext() 还返回 setActionResult()serializeActionResult() 函数以编程方式设置 Astro.getActionResult() 返回的值:

¥Start by creating a middleware file and importing the getActionContext() utility from astro:actions. This function returns an action object with information about the incoming action request, including the action handler and whether the action was called from an HTML form. getActionContext() also returns the setActionResult() and serializeActionResult() functions to programmatically set the value returned by Astro.getActionResult():

src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
import { getActionContext } from 'astro:actions';
export const onRequest = defineMiddleware(async (context, next) => {
const { action, setActionResult, serializeActionResult } = getActionContext(context);
if (action?.calledFrom === 'form') {
const result = await action.handler();
// ... handle the action result
setActionResult(action.name, serializeActionResult(result));
}
return next();
});

持久化 HTML 表单结果的常见做法是 POST / Redirect / GET pattern。此重定向会在页面刷新时删除 “确认表单重新提交?” 对话框,并允许操作结果在整个用户会话期间保持不变。

¥A common practice to persist HTML form results is the POST / Redirect / GET pattern. This redirect removes the “confirm form resubmission?” dialog when the page is refreshed, and allows action results to be persisted throughout the user’s session.

此示例将 POST / Redirect / GET 模式应用于安装了 Netlify 服务器适配器 的会话存储的所有表单提交。操作结果使用 Netlify Blob 写入会话存储,并在重定向后使用会话 ID 检索:

¥This example applies the POST / Redirect / GET pattern to all form submissions using session storage with the Netlify server adapter installed. Action results are written to a session store using Netlify Blob, and retrieved after a redirect using a session ID:

src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
import { getActionContext } from 'astro:actions';
import { randomUUID } from "node:crypto";
import { getStore } from "@netlify/blobs";
export const onRequest = defineMiddleware(async (context, next) => {
// Skip requests for prerendered pages
if (context.isPrerendered) return next();
const { action, setActionResult, serializeActionResult } =
getActionContext(context);
// Create a Blob store to persist action results with Netlify Blob
const actionStore = getStore("action-session");
// If an action result was forwarded as a cookie, set the result
// to be accessible from `Astro.getActionResult()`
const sessionId = context.cookies.get("action-session-id")?.value;
const session = sessionId
? await actionStore.get(sessionId, {
type: "json",
})
: undefined;
if (session) {
setActionResult(session.actionName, session.actionResult);
// Optional: delete the session after the page is rendered.
// Feel free to implement your own persistence strategy
await actionStore.delete(sessionId);
context.cookies.delete("action-session-id");
return next();
}
// If an action was called from an HTML form action,
// call the action handler and redirect to the destination page
if (action?.calledFrom === "form") {
const actionResult = await action.handler();
// Persist the action result using session storage
const sessionId = randomUUID();
await actionStore.setJSON(sessionId, {
actionName: action.name,
actionResult: serializeActionResult(actionResult),
});
// Pass the session ID as a cookie
// to be retrieved after redirecting to the page
context.cookies.set("action-session-id", sessionId);
// Redirect back to the previous page on error
if (actionResult.error) {
const referer = context.request.headers.get("Referer");
if (!referer) {
throw new Error(
"Internal: Referer unexpectedly missing from Action POST request.",
);
}
return context.redirect(referer);
}
// Redirect to the destination page on success
return context.redirect(context.originPathname);
}
return next();
});

使用操作时的安全性

标题部分 使用操作时的安全性

¥Security when using actions

操作可根据操作名称作为公共端点访问。例如,操作 blog.like() 可从 /_actions/blog.like 访问。这对于单元测试操作结果和调试生产错误很有用。但是,这意味着你必须使用与 API 端点和按需渲染页面相同的授权检查。

¥Actions are accessible as public endpoints based on the name of the action. For example, the action blog.like() will be accessible from /_actions/blog.like. This is useful for unit testing action results and debugging production errors. However, this means you must use same authorization checks that you would consider for API endpoints and on-demand rendered pages.

从操作处理程序授权用户

标题部分 从操作处理程序授权用户

¥Authorize users from an action handler

要授权操作请求,请向你的操作处理程序添加身份验证检查。你可能希望使用 身份验证库 来处理会话管理和用户信息。

¥To authorize action requests, add an authentication check to your action handler. You may want to use an authentication library to handle session management and user information.

操作公开完整的 APIContext 对象以访问使用 context.locals 从中间件传递的属性。当用户未获得授权时,你可以使用 UNAUTHORIZED 代码引发 ActionError

¥Actions expose the full APIContext object to access properties passed from middleware using context.locals. When a user is not authorized, you can raise an ActionError with the UNAUTHORIZED code:

src/actions/index.ts
import { defineAction, ActionError } from 'astro:actions';
export const server = {
getUserSettings: defineAction({
handler: async (_input, context) => {
if (!context.locals.user) {
throw new ActionError({ code: 'UNAUTHORIZED' });
}
return { /* data on success */ };
}
})
}

来自中间件的门控操作

标题部分 来自中间件的门控操作

¥Gate actions from middleware

Added in: astro@5.0.0

Astro 建议从你的操作处理程序授权用户会话,以尊重每个操作的权限级别和速率限制。但是,你也可以从中间件将所有操作(或操作子集)的请求控制到门控。

¥Astro recommends authorizing user sessions from your action handler to respect permission levels and rate-limiting on a per-action basis. However, you can also gate requests to all actions (or a subset of actions) from middleware.

使用中间件中的 getActionContext() 函数检索有关任何入站操作请求的信息。这包括操作名称以及该操作是使用客户端远程过程调用 (RPC) 函数(例如 actions.blog.like())还是 HTML 表单调用的。

¥Use the getActionContext() function from your middleware to retrieve information about any inbound action requests. This includes the action name and whether that action was called using a client-side remote procedure call (RPC) function (e.g. actions.blog.like()) or an HTML form.

以下示例拒绝所有没有有效会话令牌的操作请求。如果检查失败,则返回 “禁止” 响应。注意:此方法可确保仅在存在会话时才可访问操作,但不能替代安全授权。

¥The following example rejects all action requests that do not have a valid session token. If the check fails, a “Forbidden” response is returned. Note: this method ensures that actions are only accessible when a session is present, but is not a substitute for secure authorization.

src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
import { getActionContext } from 'astro:actions';
export const onRequest = defineMiddleware(async (context, next) => {
const { action } = getActionContext(context);
// Check if the action was called from a client-side function
if (action?.calledFrom === 'rpc') {
// If so, check for a user session token
if (context.cookies.has('user-session')) {
return new Response('Forbidden', { status: 403 });
}
}
context.cookies.set('user-session', /* session token */);
return next();
});

从 Astro 组件和服务器端点调用操作

标题部分 从 Astro 组件和服务器端点调用操作

¥Call actions from Astro components and server endpoints

你可以使用 Astro.callAction() 封装器(或使用 服务器端点 时使用 context.callAction())直接从 Astro 组件脚本调用操作。这通常用于在其他服务器代码中重用操作中的逻辑。

¥You can call actions directly from Astro component scripts using the Astro.callAction() wrapper (or context.callAction() when using a server endpoint). This is common to reuse logic from your actions in other server code.

将操作作为第一个参数传递,将任何输入参数作为第二个参数传递。这将返回你在客户端上调用操作时收到的相同 dataerror 对象:

¥Pass the action as the first argument and any input parameters as the second argument. This returns the same data and error objects you receive when calling actions on the client:

src/pages/products.astro
---
import { actions } from 'astro:actions';
const searchQuery = Astro.url.searchParams.get('search');
if (searchQuery) {
const { data, error } = Astro.callAction(actions.findProduct, { query: searchQuery });
// handle result
}
---
Astro 中文网 - 粤ICP备13048890号