# Server Actions

Type-safe server actions with createAction — automatic header forwarding for Next.js Server Actions.

## Overview

`createAction` from `@noverax/courier-next` creates a dot-notation client for use in Next.js Server Actions. It works like `createCaller`, but automatically forwards the incoming request's headers via `next/headers`: `authorization` and the other safe ones, the headers the source and the endpoint name, and the user's cookies where they name `cookie`.

```ts title="lib/actions.ts"
import { createAction } from "@noverax/courier-next"
import { sources } from "@/api"

export const action = createAction(sources, "api")
```

```ts title="app/actions/users.ts"
"use server"

import { revalidatePath } from "next/cache"
import { action } from "@/lib/actions"

export async function createUser(data: { name: string; email: string }) {
  const user = await action.users.create(data)
  revalidatePath("/users")
  return user
}
```

## API signature

```ts
import type { InferClient } from "@noverax/courier"
import type { Sources } from "@noverax/courier/define"
import type { ActionDefaults } from "@noverax/courier-next"

declare function createAction<TSources extends Sources, TKey extends string & keyof TSources>(
  sources: TSources,
  source: TKey,
  defaults?: ActionDefaults,
): InferClient<TSources[TKey]>
```

| Parameter | Description |
|---|---|
| `sources` | Sources map (same object passed to `apiRoute`) |
| `source` | Source name — key in the sources map |
| `defaults` | Optional configuration |

## ActionDefaults

```ts
type ActionDefaults = {
  // Forward incoming request headers to the API (default: true)
  headers?: boolean
}
```

> **The action's request**
>
> `createAction` reads the action's own request through `next/headers`. An API that keeps a `session` gets the user's session from it — the cookies the session names, and the double-submit token a write repeats — and the session cookies the API sets reach the browser with the action's response; see [User Sessions](/docs/server-side/session). By default the request's headers are forwarded too, through the same allowlist a context goes through; `headers: false` turns that off, and leaves the session to the API that keeps one.

## Basic usage

Create the action client once and use it across your server action files:

```ts title="lib/actions.ts"
import { createAction } from "@noverax/courier-next"
import { sources } from "@/api"

export const action = createAction(sources, "api")
```

```ts title="app/actions/users.ts"
"use server"

import { revalidatePath } from "next/cache"
import { action } from "@/lib/actions"

export async function createUser(data: { name: string; email: string }) {
  const user = await action.users.create(data)
  revalidatePath("/users")
  return user
}

export async function updateUser(data: { id: number; name: string; email: string }) {
  const user = await action.users.update(data)
  revalidatePath("/users")
  return user
}

export async function deleteUser(id: number) {
  await action.users.remove({ id })
  revalidatePath("/users")
  revalidatePath("/dashboard")
}
```

## ActionState

`ActionState` is a convenience type exported from `@noverax/courier-next` for structuring server action return values with `useActionState`:

```ts
type ActionState<T = unknown> = {
  data: T | null
  error: {
    message: string
    status?: number
    issues?: readonly ValidationIssue[]  // Standard Schema validation errors
  } | null
}
```

You can use it to wrap your server actions with consistent error handling:

```ts title="app/actions/users.ts"
"use server"

import type { ActionState } from "@noverax/courier-next"
import { revalidatePath } from "next/cache"
import { HTTPError } from "@noverax/courier"
import { action } from "@/lib/actions"

export async function createUser(
  _prev: ActionState,
  formData: FormData,
): Promise<ActionState> {
  try {
    const data = {
      name: formData.get("name") as string,
      email: formData.get("email") as string,
    }

    const user = await action.users.create(data)
    revalidatePath("/users")
    return { data: user, error: null }
  } catch (error) {
    if (error instanceof HTTPError) {
      return { data: null, error: { message: error.message, status: error.status } }
    }
    return { data: null, error: { message: "Unknown error" } }
  }
}
```

## Using with useActionState

```tsx title="app/users/create-form.tsx"
"use client"

import { useActionState } from "react"
import { createUser } from "@/app/actions/users"

export function CreateUserForm() {
  const [state, formAction, isPending] = useActionState(createUser, {
    data: null,
    error: null,
  })

  return (
    <form action={formAction}>
      <input name="name" placeholder="Name" required />
      <input name="email" type="email" placeholder="Email" required />
      <button type="submit" disabled={isPending}>
        {isPending ? "Creating..." : "Create User"}
      </button>

      {state.error && <p className="text-red-500">{state.error.message}</p>}

      {state.error?.issues && (
        <ul>
          {state.error.issues.map((issue, i) => (
            <li key={i}>{issue.message}</li>
          ))}
        </ul>
      )}

      {state.data && <p className="text-green-500">Created: {state.data.name}</p>}
    </form>
  )
}
```

## Difference from createCaller

Both `createAction` and `createCaller` return a `TypedClient` with dot-notation access. The difference:

- **`createServerCaller(api)`** — takes a single `DefinedApi`; its calls carry the session of the request Next.js is serving, wherever they are made — a page, a layout, a Server Action, a Route Handler
- **`createAction(sources, "api")`** — takes `Sources` map + source name; reads the action's own request, its headers forwarded through the allowlist

```ts
// createServerCaller — the session of the request being served
const created = await server.users.create(data)

// createAction — the action's request, its headers forwarded too
const forwarded = await action.users.create(data)
```

Use either in Server Actions. Use `createServerCaller` in Server Components and Route Handlers.

## Server Actions vs useMutation

Both Server Actions and React Query's `useMutation` can handle mutations. They serve different use cases:

- **Server Actions** — best for form submissions, progressive enhancement, and when you want Next.js to handle revalidation
- **useMutation** — best for optimistic updates, cache invalidation via query keys, and when you need React Query's retry/rollback features

```ts title="app/users/create-button.tsx"
"use client"

import { q } from "@/lib/query"

export function CreateUserButton() {
  // useMutation calls the proxy endpoint, not a Server Action
  // Use this when you want React Query's cache management
  const { mutate, isPending } = q.users.useMutation("create", {
    onSuccess: (user) => {
      console.log("Created:", user.name)
    },
  })

  return (
    <button
      onClick={() => mutate({ name: "Jane", email: "jane@test.com", role: "user" })}
      disabled={isPending}
    >
      {isPending ? "Creating..." : "Add User"}
    </button>
  )
}
```

## Next Steps

- [createCaller](/docs/server-side/caller) — full API reference with defaults and raw data mode
- [useMutation](/docs/react-query/use-mutation) — React Query mutations with proxy mode
- [Error Handling](/docs/core-concepts/error-handling) — HTTPError structure and error callbacks
