Skip to content
Oxide
Esc
navigateopen⌘Jpreview
On this page

Server Actions

Write a function on the server and call it from the browser - types stay in sync, the function never ships to the client.

Server actions ride on tacho. Install it next to oxidejs:

npm install tacho
pnpm add tacho
yarn add tacho
bun add tacho

Files named *.server.ts / *.server.js are server-only. A client import is replaced with a tacho stub that POSTs /_action. The original module never enters the client graph. Server and Vite SSR (import.meta.env.SSR === true) keep the real functions. Method names are <file>.<fn> (test.ping).

export async function function ping(): Promise<"pong">ping() {
  return "pong" as type const = "pong"const;
}
// @filename: client.ts
import { function ping(): Promise<"pong">ping } from "./test.server";

const const result: "pong"result = await function ping(): Promise<"pong">ping();

async function* exports stream over tacho SSE. oxidejs/tsconfig makes await ticks() typecheck. vite dev and rsbuild dev serve /_action via middleware. Changing a *.server.ts file reloads that handler on both.

// @filename: test.server.ts
export async function* function ticks(n: number): AsyncGenerator<number, void, unknown>ticks(n: numbern: number) {
  for (let let i: numberi = 0; let i: numberi < n: numbern; let i: numberi++) yield let i: numberi;
}

Abort

Pass { signal } last on any action — stream or unary. Types come from the real *.server.ts, so declare the last argument there:

// @filename: abort.ts
import { function ping(_opts?: ActionOptions): Promise<"pong">ping, function ticks(n: number, _opts?: ActionOptions): AsyncGenerator<number, void, unknown>ticks } from "./test.server";

const const ac: AbortControllerac = new var AbortController: new () => AbortController
The **`AbortController`** interface represents a controller object that allows you to abort one or more Web requests as and when desired. [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController)
AbortController
();
const const stream: AsyncGenerator<number, void, unknown>stream = await function ticks(n: number, _opts?: ActionOptions): AsyncGenerator<number, void, unknown>ticks(10, { signal?: AbortSignal | undefinedsignal: const ac: AbortControllerac.AbortController.signal: AbortSignal
The **`signal`** read-only property of the AbortController interface returns an AbortSignal object instance, which can be used to communicate with/abort an asynchronous operation as desired. [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/signal)
signal
});
await function ping(_opts?: ActionOptions): Promise<"pong">ping({ signal?: AbortSignal | undefinedsignal: const ac: AbortControllerac.AbortController.signal: AbortSignal
The **`signal`** read-only property of the AbortController interface returns an AbortSignal object instance, which can be used to communicate with/abort an asynchronous operation as desired. [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/signal)
signal
});
const ac: AbortControllerac.AbortController.abort(reason?: any): void (+1 overload)
The **`abort()`** method of the AbortController interface aborts an asynchronous operation before it has completed. This is able to abort fetch requests, the consumption of any response bodies, or streams. [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/abort)
abort
();

useRequest().signal follows that abort. Breaking a for await also cancels.

useRequest

Call useRequest() inside an action for the inbound Request. It throws outside /_action.

import { function useRequest(): Request
Current action `Request`. Throws outside `*.server.ts` running over `/_action`.
useRequest
} from "oxidejs";
export async function function who(): Promise<string | null>who() { return function useRequest(): Request
Current action `Request`. Throws outside `*.server.ts` running over `/_action`.
useRequest
().Request.headers: Headers
The **`headers`** read-only property of the Request interface contains the Headers object associated with the request. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/headers)
headers
.Headers.get(name: string): string | null
The **`get()`** method of the Headers interface returns a byte string of all the values of a header within a Headers object with a given name. If the requested header doesn't exist in the Headers object, it returns null. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/get)
get
("x-user");
}

useCtx

useCtx() is tacho ctx: { req } plus anything middleware or createContext added.

import { function useCtx<C extends ActionContext = ActionContext>(): C
Current tacho `ctx`. Throws outside `*.server.ts` running over `/_action`.
useCtx
} from "oxidejs";
export async function function me(): Promise<string | undefined>me() { return
useCtx<{
    req: Request;
    user?: string;
}>(): {
    req: Request;
    user?: string;
}
Current tacho `ctx`. Throws outside `*.server.ts` running over `/_action`.
useCtx
<{ req: Requestreq: Request; user?: string | undefineduser?: string }>().user?: string | undefineduser;
}

useEnv and useFetchCtx

On preset: "celld", useEnv() and useFetchCtx() are the Worker env and ctx from fetch(request, env, ctx) — same values as useCtx().env / useCtx().fetchCtx. They are undefined on the Node fetch preset.

import { function useEnv<E = unknown>(): E | undefined
Worker `env` from `fetch(request, env, ctx)`. `undefined` on the Node fetch preset.
useEnv
, function useFetchCtx(): ExecutionContext | undefined
Worker `ctx` from `fetch(request, env, ctx)` (`waitUntil`). Not tacho `ctx`. `undefined` on Node.
useFetchCtx
} from "oxidejs";
type
type Env = {
    SECRET: string;
}
Env
= { type SECRET: stringSECRET: string };
export async function function secret(): Promise<string | undefined>secret() { function useFetchCtx(): ExecutionContext | undefined
Worker `ctx` from `fetch(request, env, ctx)` (`waitUntil`). Not tacho `ctx`. `undefined` on Node.
useFetchCtx
()?.waitUntil?(promise: Promise<unknown>): voidwaitUntil?.(var Promise: PromiseConstructor
Represents the completion of an asynchronous operation
Promise
.PromiseConstructor.resolve(): Promise<void> (+2 overloads)
Creates a new resolved promise.
@returnsA resolved promise.
resolve
());
return useEnv<Env>(): Env | undefined
Worker `env` from `fetch(request, env, ctx)`. `undefined` on the Node fetch preset.
useEnv
<
type Env = {
    SECRET: string;
}
Env
>()?.type SECRET: string | undefinedSECRET;
}

Transport

Stubs share one tacho client. Pass static headers with actionHeaders. Functions cannot ship to the browser.

import const oxide: (options?: OxidejsOptions | undefined) => import("vite").Plugin<any> | import("vite").Plugin<any>[]oxide from "oxidejs/vite";

function oxide(options?: OxidejsOptions | undefined): import("vite").Plugin<any> | import("vite").Plugin<any>[]oxide({
  OxidejsOptions.actionHeaders?: OxidejsActionHeaders | undefined
Extra headers on the shared HTTP action client. Ignored when `actions` is "ws".
actionHeaders
: { authorization: stringauthorization: "Bearer x" },
});

actions: "ws" upgrades /_action to a WebSocket. Install crossws. It does not work with preset: "celld".

import const oxide: (options?: OxidejsOptions | undefined) => import("vite").Plugin<any> | import("vite").Plugin<any>[]oxide from "oxidejs/vite";

function oxide(options?: OxidejsOptions | undefined): import("vite").Plugin<any> | import("vite").Plugin<any>[]oxide({
  OxidejsOptions.actions?: OxidejsActionTransport | undefined
Transport for `*.server.ts` stubs. Default: "http".
actions
: "ws",
});

Was this page helpful?