Installation & Setup Guide

View Source

This guide provides detailed instructions for installing and configuring AshRpc in your Phoenix application.

⚠️ EXPERIMENTAL WARNING: AshRpc is still in early development and considered highly experimental. Breaking changes may occur frequently without notice. We strongly advise against using this package in production environments until it reaches a stable release (v1.0.0+).

Prerequisites

Before installing AshRpc, ensure you have:

  • Elixir 1.15+
  • Phoenix 1.7+
  • Ash Framework 3.0+ (already configured in your application)
  • PostgreSQL or another supported database

Installation Options

The easiest way to get started is using the automated installer:

# Add ash_rpc to your dependencies using Igniter (if installed)
mix igniter.install ash_rpc

# Or manually add to your mix.exs:
# {:ash_rpc, "~> 0.1"}

# Install dependencies
mix deps.get

# Run the installer
mix ash_rpc.install

The installer will:

  • ✅ Generate MyAppWeb.TrpcRouter module
  • ✅ Add tRPC pipeline to your Phoenix router
  • ✅ Configure route forwarding to /trpc
  • ✅ Format generated files

Option 2: Manual Installation

For more control over the installation process:

Step 1: Add Dependencies

Add AshRpc to your mix.exs:

defp deps do
  [
    ...,
    {:ash_rpc, "~> 0.1"},
  ]
end

Step 2: Install Dependencies

mix deps.get

Step 3: Create tRPC Router

Create a new file lib/my_app_web/trpc_router.ex:

defmodule MyAppWeb.TrpcRouter do
  use AshRpc.Router,
    domains: [MyApp.Accounts, MyApp.Billing],
    # Optional: Custom transformer for input/output processing
    transformer: AshRpc.Output.Transformer.Identity,
    # Optional: Before hooks
    before: [],
    # Optional: After hooks
    after: [],
    # Optional: Context creation function
    create_context: &AshRpc.Web.Controller.default_context/1
end

Step 4: Configure Phoenix Router

Add the tRPC pipeline and routes to your lib/my_app_web/router.ex:

defmodule MyAppWeb.Router do
  use MyAppWeb, :router

  # ... existing pipelines

  # tRPC pipeline - JSON only
  pipeline :ash_rpc do
    plug :accepts, ["json"]
    # Add authentication plugs here if needed
    # plug :retrieve_from_bearer
    # plug :set_actor, :user
  end

  # ... existing routes

  # Mount tRPC under the :trpc pipeline
  scope "/trpc" do
    pipe_through :ash_rpc
    forward "/", MyAppWeb.TrpcRouter
  end
end

Configuration Options

Router Options

defmodule MyAppWeb.TrpcRouter do
  use AshRpc.Router,
    # Required: Domains to expose
    domains: [MyApp.Accounts, MyApp.Billing, MyApp.Notifications],

    # Optional: Custom input/output transformer
    transformer: MyApp.TrpcTransformer,

    # Optional: Before request hooks
    before: [MyApp.TrpcHooks.Logging, MyApp.TrpcHooks.Metrics],

    # Optional: After request hooks
    after: [MyApp.TrpcHooks.Audit],

    # Optional: Custom context creation function
    create_context: &MyApp.TrpcContext.create/1,

    # Optional: Custom middlewares
    middlewares: [MyApp.TrpcMiddleware.Auth, MyApp.TrpcMiddleware.Cache]
end

Custom Hooks

Create custom hooks for request processing:

# lib/my_app/trpc_hooks/logging.ex
defmodule MyApp.TrpcHooks.Logging do
  @behaviour AshRpc.Execution.Middleware

  @impl true
  def before_request(ctx) do
    Logger.info("tRPC request: #{ctx.procedure}")
    ctx
  end

  @impl true
  def after_request(ctx, result) do
    Logger.info("tRPC response: #{ctx.procedure} - #{result.status}")
    result
  end
end

Custom Context

Create custom context for requests:

# lib/my_app/trpc_context.ex
defmodule MyApp.TrpcContext do
  def create(%Plug.Conn{} = conn) do
    %{
      actor: conn.assigns[:current_user],
      tenant: conn.assigns[:current_tenant],
      request_id: Logger.metadata()[:request_id],
      user_agent: get_user_agent(conn),
      ip_address: get_ip_address(conn)
    }
  end

  defp get_user_agent(conn) do
    conn
    |> Plug.Conn.get_req_header("user-agent")
    |> List.first()
  end

  defp get_ip_address(conn) do
    conn.remote_ip
    |> :inet.ntoa()
    |> to_string()
  end
end

Resource Configuration

Basic Resource Setup

Configure your Ash resources to expose actions via tRPC:

defmodule MyApp.Accounts.User do
  use Ash.Resource,
    extensions: [AshRpc],
    domain: MyApp.Accounts

  # ... resource attributes, actions, etc.

  ash_rpc do
    # Optional: Expose actions (only needed if not using query/mutation entities below)
    # expose [:read, :create, :update, :destroy]

    # Configure specific procedures with advanced features
    query :read do
      filterable true
      sortable true
      selectable true
      paginatable true
    end

    mutation :create
    mutation :update
    mutation :destroy

    # Custom resource name (defaults to module name)
    resource_name "user"

    # Configure method overrides
    methods: [read: :query, create: :mutation]
  end
end

Advanced Query Configuration

ash_rpc do
  expose [:read, :create, :search]

  # Configure read queries
  query :read do
    filterable true      # Allow client-side filtering
    sortable true        # Allow client-side sorting
    selectable true      # Allow client-side field selection
    paginatable true     # Allow client-side pagination
    relationships [:posts, :comments, :profile]
  end

  # Custom query for specific use case
  query :search, :read do
    filterable true
    selectable false  # Disable field selection for search
    relationships []  # No relationships for search
  end

  # Configure mutations
  mutation :create, :create do
    metadata fn _subject, user, _ctx ->
      %{user_id: user.id, created_at: user.inserted_at}
    end
  end

  mutation :register, :register_with_password do
    metadata fn _subject, user, _ctx ->
      %{token: user.__metadata__.token, user_id: user.id}
    end
  end
end

Testing the Installation

1. Verify Router Setup

Start your Phoenix server:

mix phx.server

2. Test Health Check

Make a simple HTTP request to verify the router is working:

curl -X POST http://localhost:4000/trpc \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"query","params":{}}'

You should receive a tRPC-compatible response.

3. Configure Resources

Add tRPC configuration to at least one resource:

defmodule MyApp.Accounts.User do
  use Ash.Resource, extensions: [AshRpc]

  ash_rpc do
    expose [:read]
  end

  # ... rest of resource
end

4. Test Resource Endpoint

curl -X POST http://localhost:4000/trpc/accounts.user.read \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"query","params":{}}'

Troubleshooting

Common Issues

1. Router Not Found Error

Error: UndefinedFunctionError: function MyAppWeb.TrpcRouter.init/1 is undefined

Solution: Ensure the router module is properly defined and compiled:

mix compile
mix phx.server

2. Domain Not Found Error

Error: Ash.Error.Invalid: Domain MyApp.Accounts not found

Solution: Verify the domain is correctly configured in your router:

# Check domain exists
MyApp.Accounts

# Verify router configuration
defmodule MyAppWeb.TrpcRouter do
  use AshRpc.Router, domains: [MyApp.Accounts]  # Correct module name
end

3. Resource Not Exposed Error

Error: AshRpc.Error: Resource MyApp.Accounts.User not exposed

Solution: Add tRPC configuration to your resource:

defmodule MyApp.Accounts.User do
  use Ash.Resource, extensions: [AshRpc]

  ash_rpc do
    expose [:read]  # Add this block
  end
end

4. Authentication Issues

Error: Ash.Error.Forbidden: Access denied

Solution: Configure authentication in your router pipeline:

pipeline :ash_rpc do
  plug :accepts, ["json"]
  plug :retrieve_from_bearer  # Add this
  plug :set_actor, :user      # Add this
end

Debug Mode

Enable detailed logging for troubleshooting:

# config/dev.exs
config :logger, level: :debug

config :ash_rpc, debug: true

Next Steps

Once installation is complete:

  1. Generate TypeScript Types:

    mix ash_rpc.codegen --output=./frontend/generated --zod
    
  2. Set Up Authentication (see Authentication Guide)

Support

If you encounter issues during installation: