DEV Community

Alex Spinov
Alex Spinov

Posted on

Astro Actions Has a Free API — Heres How to Add Type-Safe Server Functions

Astro Actions let you define type-safe server functions that you call directly from your frontend components — no REST endpoints, no GraphQL, no boilerplate.

Why Astro Actions?

  • Type-safe: Full TypeScript inference from server to client
  • Zero boilerplate: Define a function, call it from any component
  • Built-in validation: Zod schemas for input validation
  • Framework-agnostic: Works with React, Vue, Svelte, Solid inside Astro
  • Progressive enhancement: Works with and without JavaScript

Setup

Actions are built into Astro 4.8+. No extra packages needed.

npm create astro@latest my-app
Enter fullscreen mode Exit fullscreen mode

Define Actions

Create src/actions/index.ts:

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

export const server = {
  createPost: defineAction({
    accept: 'json',
    input: z.object({
      title: z.string().min(1).max(200),
      body: z.string().min(10),
      tags: z.array(z.string()).optional(),
    }),
    handler: async (input) => {
      const post = await db.post.create({ data: input });
      return { id: post.id, slug: post.slug };
    },
  }),

  deletePost: defineAction({
    accept: 'json',
    input: z.object({ id: z.string() }),
    handler: async ({ id }) => {
      await db.post.delete({ where: { id } });
      return { success: true };
    },
  }),
};
Enter fullscreen mode Exit fullscreen mode

Call from React Component

import { actions } from 'astro:actions';

export function CreatePostForm() {
  async function handleSubmit(e) {
    e.preventDefault();
    const formData = new FormData(e.target);
    const { data, error } = await actions.createPost({
      title: formData.get('title'),
      body: formData.get('body'),
    });
    if (error) console.error(error.message);
    else window.location.href = `/posts/${data.slug}`;
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="title" placeholder="Title" />
      <textarea name="body" placeholder="Content" />
      <button type="submit">Publish</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

Form Actions (Progressive Enhancement)

Works without JavaScript:

---
import { actions } from 'astro:actions';
---
<form method="POST" action={actions.createPost}>
  <input name="title" />
  <textarea name="body" />
  <button type="submit">Create</button>
</form>
Enter fullscreen mode Exit fullscreen mode

Error Handling

const { data, error } = await actions.createPost({ title: '', body: 'test' });

if (error) {
  if (error.code === 'INPUT_VALIDATION_ERROR') {
    console.log(error.fields); // { title: ['String must contain at least 1 character'] }
  }
}
Enter fullscreen mode Exit fullscreen mode

Real-World Use Case

A developer replaced 15 API route files with 8 Astro Actions. Type safety caught 3 bugs during migration that the old REST endpoints had missed for months.


Need to automate data collection for YOUR project? Check out my Apify actors for ready-made scrapers, or email spinov001@gmail.com for custom solutions.

Top comments (0)