elixir_ts_rpc

Typed RPC between Elixir and TypeScript. One @spec is the whole contract. It validates requests at runtime and generates your TypeScript client. There is no second schema to keep in sync.

๐Ÿงช Playground โ†’ edit a @spec, watch the client regenerate runs this codegen in your browser. Nothing to install.

How it looks

Write a handler with a normal @spec:

defmodule MyApp.Handlers.Users do
use RpcElixir.Handler
@spec get(%{id: integer()}, RpcElixir.Context.t()) ::
{:ok, %{id: integer(), name: String.t()}} | {:error, :not_found}
def get(%{id: id}, _ctx), do: MyApp.Users.fetch(id)
end

Expose the module on a router:

defmodule MyApp.RpcRouter do
use RpcElixir.Router
scope "users" do
expose MyApp.Handlers.Users # "users.get", "users.list", ...
end
end

Every public, @spec'd, arity-2 function of the module is published, named after the function. Add another one to the handler and it appears on the next compile. When you need a different wire name, a subset of a module, or per-function middleware, name procedures one at a time with procedure instead. See RpcElixir.Router.

mix rpc.gen.ts reads that spec from BEAM debug info. It then writes a typed client:

const user = await client.users.get({ id: 1 });
// ^? { id: number; name: string }

Why

Weighing it against Absinthe, OpenAPI codegen, LiveView, or hand-written endpoints? See Comparison.

Scope

Pre-1.0 (0.0.2), so APIs may change. You need Elixir ~> 1.17 on OTP 26+. Elixir 1.19+ is recommended. On 1.17 add {:jason, "~> 1.4"} to your deps, since Elixir's built-in JSON module only arrived in 1.18. Transport is HTTP request/response only. There is no SSE, Channels, or WebSocket transport yet, and React is the only framework adapter. Full list: what works today.

Install

# mix.exs
def deps do
[{:elixir_ts_rpc, "~> 0.0.2"}]
end

The Hex package and OTP app are :elixir_ts_rpc. The module namespace is RpcElixir.*.

Next: Getting started.

Documentation

The server side lives here on HexDocs:

The client side lives on the guide site:

License

MIT.