Getting Started with Actyx RPC
Actyx RPC is a type-safe RPC framework for building composable server actions in TypeScript. It allows you to build standard, type-safe API boundaries and hooks for React applications with minimal boilerplate and built-in caching.
- 🔒 End-to-end type safety: Type inference flows from server procedures directly to client hooks.
- ⚡ Built for server actions: Works seamlessly with Next.js Server Actions and standard route handlers.
- 🧩 Composable middleware & plugins: Chain middlewares to extend context and validate payloads.
- 🧠 Flexible input modes: Configure strict, partial, or form data input schemas.
- 🛡️ Resilience: Out-of-the-box support for Retries, Timeouts, Rate Limits, and Circuit Breakers.
- 📊 Observability: Built-in OpenTelemetry instrumentation and telemetry plugin tracking.
- 🔌 Resolver agnostic: Integrates with Zod, Valibot, ArkType, Joi, Yup, or custom validation functions.
Why Actyx?
Traditional APIs force you to choose between flexibility and type safety. Actyx gives you both.
Define a procedure once, and get:
- Fully typed inputs
- Validated payload structures
- Reusable logic layers across your application
No code generation steps. No duplicate schemas leaking across directories. Just clean, predictable, and typed server actions.
Package Architecture
Actyx RPC is split into two specialized, lightweight packages:
| Package | Purpose | Typical Environment |
|---|---|---|
@explita/actyx-rpc | Core RPC engine, procedure builders, middleware, execution policies, validation resolvers, and server adapters | Node.js, Next.js Server Actions, Express, Cloudflare Workers |
@explita/actyx-rpc-react | React integration, built-in QueryClient, in-memory cache, reactive hooks (useQuery, useMutation, etc.), and streaming clients | Next.js Client Components, Vite, React SPA |
Installation
Choose the packages you need based on your architecture:
Fullstack (Next.js, Remix, TanStack Start)
Install both packages to build server procedures and consume them in React:
npm install @explita/actyx-rpc @explita/actyx-rpc-reactBackend / Server API Only
If you are building a standalone API or server actions without React client hooks:
npm install @explita/actyx-rpcFrontend / React App Only
If you are consuming Actyx RPC procedures from a React client application:
npm install @explita/actyx-rpc-reactPeer Dependencies
Install your schema validation library of choice (e.g. zod, valibot, arktype, joi, or yup):
npm install zodQuick Start
1. Define Server Procedures (@explita/actyx-rpc)
import { createProcedure } from "@explita/actyx-rpc";
import { z } from "zod";
import { zodResolver } from "@explita/actyx-rpc/resolvers/zod";
const procedure = createProcedure({
async createContext() {
return {
ok: true,
ctx: {
userId: "user_123",
role: "admin",
},
};
},
});
// Create a query procedure
export const getPost = procedure
.input(
zodResolver(
z.object({
id: z.string().min(1, "Post id is required"),
}),
),
)
.query(async ({ ctx, input }) => {
return {
id: input.id,
title: "Hello from Actyx RPC",
authorId: ctx.userId,
};
});2. Consume in React (@explita/actyx-rpc-react)
Wrap your application in ActyxProvider and call the procedure directly in components:
"use client";
import { QueryClient, ActyxProvider, useQuery } from "@explita/actyx-rpc-react";
import { getPost } from "@/server/procedures";
const queryClient = new QueryClient({
queries: {
staleTime: "5m", // cache fresh for 5 minutes
},
});
export function App() {
return (
<ActyxProvider client={queryClient}>
<PostDetail postId="post_1" />
</ActyxProvider>
);
}
function PostDetail({ postId }: { postId: string }) {
const { data: post, isLoading, isError, error } = useQuery(
() => getPost({ id: postId }),
{ queryKey: ["post", postId] }
);
if (isLoading) return <div>Loading post...</div>;
if (isError) return <div>Error: {error?.message}</div>;
return <h1>{post.title}</h1>;
}3. Or Use the Client Proxy & API Endpoint
If you prefer calling your procedures via an HTTP API endpoint (/api/rpc/...) rather than direct Server Action imports:
// 1. Define your router (backend/router.ts)
import { createRouter } from "@explita/actyx-rpc";
import { getPost } from "./procedures";
export const appRouter = createRouter({
posts: createRouter({
get: getPost,
}),
});
export type AppRouter = typeof appRouter;// 2. Mount the router in Next.js App Router (app/api/rpc/[...rpc]/route.ts)
import { createHandler } from "@explita/actyx-rpc/adapters/next";
import { appRouter } from "@/backend/router";
export const { GET, POST } = createHandler(appRouter);// 3. Initialize the client proxy (lib/rpc.ts)
import { createClient } from "@explita/actyx-rpc-react";
import type { AppRouter } from "@/backend/router";
export const rpc = createClient<AppRouter>({ baseUrl: "/api/rpc" });
// 4. Call procedures directly or with hooks anywhere in your app:
function MyComponent() {
const { data: post, isLoading } = rpc.posts.get.useQuery({ id: "post_1" });
if (isLoading) return <div>Loading...</div>;
return <h1>{post?.title}</h1>;
}Documentation Sections
Support the Mission
Actyx RPC is built to simplify building type-safe, distributed systems with minimal boilerplate. If it has helped you build better APIs faster, please consider supporting the project to ensure its continued growth and maintenance!
- Sponsor on GitHub
- Buy Me A Coffee
- Give us a ⭐ — It helps others discover the project.
- Report bugs or suggest new features .
License
MIT © Explita