Convert Figma logo to code with AI

elixir-grpc logogrpc

An Elixir implementation of gRPC

1,497
238
1,497
32

Top Related Projects

43,926

C++ based gRPC (C++, Python, Ruby, Objective-C, PHP, C#)

gRPC to JSON proxy generator following the gRPC HTTP spec

7,458

A simple RPC framework with protobuf service definitions

gRPC Web implementation for Golang and TypeScript

Generate message validators from .proto annotations.

12,203

Like cURL, but for gRPC: Command-line tool for interacting with gRPC servers

Quick Overview

The elixir-grpc/grpc repository is an Elixir implementation of gRPC, a high-performance, open-source universal RPC framework. It allows developers to build efficient and scalable distributed systems using Elixir, leveraging the power of Protocol Buffers for serialization and HTTP/2 for transport.

Pros

  • Seamless integration with Elixir's ecosystem and OTP
  • High performance and low latency communication
  • Strong typing and code generation from Protocol Buffer definitions
  • Support for bidirectional streaming

Cons

  • Learning curve for developers new to gRPC or Protocol Buffers
  • Limited ecosystem compared to REST APIs in Elixir
  • Potential complexity in setup and configuration
  • Less human-readable than JSON-based APIs

Code Examples

  1. Defining a simple gRPC service:
defmodule Greeter.Service do
  use GRPC.Service, name: "Greeter"

  rpc :SayHello, HelloRequest, HelloReply
end

defmodule HelloRequest do
  use Protobuf, syntax: :proto3

  @derive Jason.Encoder
  defstruct [:name]

  field :name, 1, type: :string
end

defmodule HelloReply do
  use Protobuf, syntax: :proto3

  @derive Jason.Encoder
  defstruct [:message]

  field :message, 1, type: :string
end
  1. Implementing the service:
defmodule Greeter.Server do
  use GRPC.Server, service: Greeter.Service

  @spec say_hello(HelloRequest.t(), GRPC.Server.Stream.t()) :: HelloReply.t()
  def say_hello(request, _stream) do
    HelloReply.new(message: "Hello #{request.name}")
  end
end
  1. Starting the gRPC server:
defmodule MyApp do
  use Application

  def start(_type, _args) do
    children = [
      {GRPC.Server.Supervisor, {Greeter.Server, 50051}}
    ]

    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

Getting Started

  1. Add dependencies to mix.exs:
defp deps do
  [
    {:grpc, "~> 0.5.0"},
    {:protobuf, "~> 0.10.0"},
    {:google_protos, "~> 0.1"}
  ]
end
  1. Define your service in a .proto file
  2. Generate Elixir code from the .proto file:
protoc --elixir_out=plugins=grpc:./lib your_service.proto
  1. Implement your service and start the gRPC server as shown in the code examples above
  2. Run your application: mix run --no-halt

Competitor Comparisons

43,926

C++ based gRPC (C++, Python, Ruby, Objective-C, PHP, C#)

Pros of grpc

  • Broader language support, including C++, Java, Python, Go, and more
  • More comprehensive documentation and examples
  • Larger community and ecosystem, leading to better support and resources

Cons of grpc

  • More complex setup and configuration
  • Steeper learning curve for beginners
  • Heavier footprint and potentially slower performance in some scenarios

Code Comparison

grpc (C++):

#include <grpcpp/grpcpp.h>
#include "helloworld.grpc.pb.h"

class GreeterServiceImpl final : public Greeter::Service {
  Status SayHello(ServerContext* context, const HelloRequest* request,
                  HelloReply* reply) override {
    std::string prefix("Hello ");
    reply->set_message(prefix + request->name());
    return Status::OK;
  }
};

elixir-grpc (Elixir):

defmodule Greeter.Server do
  use GRPC.Server, service: Helloworld.Greeter.Service

  @spec say_hello(Helloworld.HelloRequest.t, GRPC.Server.Stream.t) ::
    Helloworld.HelloReply.t
  def say_hello(request, _stream) do
    Helloworld.HelloReply.new(message: "Hello #{request.name}")
  end
end

Both repositories provide gRPC implementations, but grpc offers a more comprehensive solution with wider language support, while elixir-grpc focuses specifically on Elixir integration. The choice between them depends on the project's requirements and the development team's expertise.

gRPC to JSON proxy generator following the gRPC HTTP spec

Pros of grpc-gateway

  • Provides RESTful API endpoints for gRPC services, allowing easier integration with existing REST-based systems
  • Supports automatic OpenAPI (Swagger) documentation generation
  • Language-agnostic, can be used with any gRPC implementation

Cons of grpc-gateway

  • Adds complexity to the overall architecture with an additional layer
  • May introduce slight performance overhead due to protocol translation
  • Requires additional configuration and maintenance

Code Comparison

grpc-gateway:

syntax = "proto3";
package example;
import "google/api/annotations.proto";

service ExampleService {
  rpc Echo(StringMessage) returns (StringMessage) {
    option (google.api.http) = {
      post: "/v1/example/echo"
      body: "*"
    };
  }
}

grpc (Elixir):

defmodule ExampleService do
  use GRPC.Server, service: Example.ExampleService.Service

  @spec echo(Example.StringMessage.t, GRPC.Server.Stream.t) :: Example.StringMessage.t
  def echo(request, _stream) do
    Example.StringMessage.new(value: request.value)
  end
end

The grpc-gateway example shows how to define RESTful endpoints in the protobuf definition, while the Elixir gRPC example demonstrates the implementation of a gRPC service directly in Elixir code.

7,458

A simple RPC framework with protobuf service definitions

Pros of Twirp

  • Simpler and more lightweight than gRPC
  • Supports both Protocol Buffers and JSON encoding
  • Easier to set up and use, especially for smaller projects

Cons of Twirp

  • Less feature-rich compared to gRPC
  • Limited language support (primarily Go and some community-driven implementations)
  • Lacks built-in streaming capabilities

Code Comparison

Twirp (Go):

service Haberdasher {
  rpc MakeHat(Size) returns (Hat);
}

gRPC (Elixir):

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

Key Differences

  • Twirp is designed to be a simpler alternative to gRPC, focusing on ease of use and HTTP/1.1 compatibility
  • gRPC offers more advanced features like bi-directional streaming and HTTP/2 support
  • Twirp has a smaller ecosystem and community compared to gRPC
  • gRPC provides better cross-language support and is more suitable for complex, large-scale systems

Use Cases

  • Twirp: Smaller projects, microservices, or when simplicity is preferred
  • gRPC: Large-scale distributed systems, performance-critical applications, or when advanced features are required

Both projects aim to simplify RPC communication, but they cater to different needs and complexity levels. The choice between them depends on the specific requirements of your project and the desired balance between simplicity and feature richness.

gRPC Web implementation for Golang and TypeScript

Pros of grpc-web

  • Supports browser-based gRPC communication, enabling direct gRPC calls from web applications
  • Provides TypeScript support, enhancing developer experience for frontend development
  • Offers a more seamless integration with existing gRPC backend services

Cons of grpc-web

  • Limited to web-based environments, whereas grpc supports a broader range of platforms
  • May require additional setup and configuration for browser compatibility
  • Potentially less mature and less widely adopted compared to the more established grpc

Code Comparison

grpc-web:

const { HelloRequest } = require('./hello_pb.js');
const { GreeterClient } = require('./hello_grpc_web_pb.js');

const client = new GreeterClient('http://localhost:8080');
const request = new HelloRequest();
request.setName('World');

client.sayHello(request, {}, (err, response) => {
  console.log(response.getMessage());
});

grpc:

defmodule GreeterClient do
  use GRPC.Client, service: Helloworld.Greeter.Service

  def say_hello(name) do
    {:ok, channel} = GRPC.Stub.connect("localhost:50051")
    request = Helloworld.HelloRequest.new(name: name)
    {:ok, reply} = channel |> Helloworld.Greeter.Stub.say_hello(request)
    reply.message
  end
end

This comparison highlights the different approaches and language-specific implementations of gRPC clients in web and Elixir environments.

Generate message validators from .proto annotations.

Pros of go-proto-validators

  • Specifically designed for Go, offering tight integration with Go's ecosystem
  • Provides custom validation rules for protocol buffer messages
  • Lightweight and focused on validation, making it easier to integrate into existing projects

Cons of go-proto-validators

  • Limited to Go language, whereas grpc supports multiple languages including Elixir
  • Narrower scope, focusing only on validation rather than full gRPC implementation
  • Less active community and fewer contributors compared to the more widely-used grpc

Code Comparison

go-proto-validators:

message User {
  string id = 1 [(validator.field) = {length_gt: 3, length_lt: 64}];
  string email = 2 [(validator.field) = {regex: "^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$"}];
  int32 age = 3 [(validator.field) = {int_gt: 0, int_lt: 150}];
}

grpc (Elixir):

defmodule MyApp.User do
  use Protobuf, syntax: :proto3

  field :id, 1, type: :string
  field :email, 2, type: :string
  field :age, 3, type: :int32
end

Note that grpc doesn't provide built-in validation, but it offers a more comprehensive gRPC implementation across multiple languages. The go-proto-validators example shows how it adds validation rules directly to the protocol buffer definition, while the grpc example demonstrates a basic message definition in Elixir.

12,203

Like cURL, but for gRPC: Command-line tool for interacting with gRPC servers

Pros of grpcurl

  • Language-agnostic CLI tool for interacting with gRPC servers
  • Supports reflection for discovering services and methods
  • Can be used for testing and debugging gRPC services without writing client code

Cons of grpcurl

  • Limited to command-line usage, not suitable for integration into Elixir applications
  • Lacks native Elixir ecosystem integration and OTP compatibility

Code Comparison

grpc (Elixir):

defmodule MyService do
  use GRPC.Server, service: Helloworld.Greeter.Service
  
  def say_hello(request, _stream) do
    Helloworld.HelloReply.new(message: "Hello, #{request.name}!")
  end
end

grpcurl (Command-line):

grpcurl -plaintext -d '{"name": "World"}' \
  localhost:50051 helloworld.Greeter/SayHello

Key Differences

  • grpc is an Elixir-specific implementation of gRPC, while grpcurl is a language-agnostic CLI tool
  • grpc allows for seamless integration with Elixir applications and OTP, whereas grpcurl is primarily for testing and debugging
  • grpc requires writing Elixir code to define services and methods, while grpcurl can interact with existing gRPC services without additional coding

Use Cases

  • Use grpc for building gRPC services and clients within Elixir applications
  • Use grpcurl for quick testing, debugging, and exploring gRPC services without writing client code

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

gRPC Elixir

GitHub CI Hex.pm Hex Docs License Total Downloads Last Updated

gRPC Elixir is a full-featured Elixir implementation of the gRPC protocol, supporting unary and streaming RPCs, interceptors, HTTP transcoding, and TLS. This version adopts a unified stream-based model for all types of calls.

Table of contents

Installation

The package can be installed as:

def deps do
  [
    {:grpc, "~> 0.11"},
    {:protobuf, "~> 0.14"}, # optional for import wellknown google types
    {:grpc_reflection, "~> 0.2"} # optional enable grpc reflection
  ]
end

Protobuf Code Generation

Use protoc with protobuf elixir plugin or using protobuf_generate hex package to generate the necessary files.

  1. Write your protobuf file:
syntax = "proto3";

package helloworld;

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greeting
message HelloReply {
  string message = 1;
}

// The greeting service definition.
service GreetingServer {
  rpc SayUnaryHello (HelloRequest) returns (HelloReply) {}
  rpc SayServerHello (HelloRequest) returns (stream HelloReply) {}
  rpc SayBidStreamHello (stream HelloRequest) returns (stream HelloReply) {}
}
  1. Compile protos (protoc + elixir plugin):
protoc --elixir_out=plugins=grpc:./lib -I./priv/protos helloworld.proto

Server Implementation

All RPC calls must be implemented using the stream-based API, even for unary requests.

NOTE: The old API was deprecated based on GRPC.Server.send_reply/2 and direct struct returns was deprecated as of version 0.10.x.

Unary RPC using Stream API

defmodule HelloworldStreams.Server do
  use GRPC.Server, service: Helloworld.GreetingServer.Service

  alias GRPC.Stream

  alias Helloworld.HelloRequest
  alias Helloworld.HelloReply

  @spec say_unary_hello(HelloRequest.t(), GRPC.Server.Stream.t()) :: any()
  def say_unary_hello(request, materializer) do
    request
    |> GRPC.Stream.unary(materializer: materializer)
    |> GRPC.Stream.map(fn %HelloReply{} = reply ->
      %HelloReply{message: "[Reply] #{reply.message}"}
    end)
    |> GRPC.Stream.run()
  end
end

Server-Side Streaming

def say_server_hello(request, materializer) do
  Stream.repeatedly(fn ->
    index = :rand.uniform(10)
    %HelloReply{message: "[#{index}] Hello #{request.name}"}
  end)
  |> Stream.take(10)
  |> GRPC.Stream.from()
  |> GRPC.Stream.run_with(materializer)
end

Bidirectional Streaming

@spec say_bid_stream_hello(Enumerable.t(), GRPC.Server.Stream.t()) :: any()
def say_bid_stream_hello(request, materializer) do
  output_stream =
    Stream.repeatedly(fn ->
      index = :rand.uniform(10)
      %HelloReply{message: "[#{index}] Server response"}
    end)

  GRPC.Stream.from(request, join_with: output_stream)
  |> GRPC.Stream.map(fn
    %HelloRequest{name: name} -> %HelloReply{message: "Welcome #{name}"}
    other -> other
  end)
  |> GRPC.Stream.run_with(materializer)
end

The Stream API supports composable stream transformations via ask, map, run and others functions, enabling clean and declarative stream pipelines. For a complete list of available operators see here.


Effects and Error Handling

Side Effects

The effect/2 operator executes user-defined functions for each element in the stream, allowing the integration of non-transformative actions such as logging, metrics, or external notifications.

Unlike transformation operators (e.g., map/2), effect/2 does not modify or filter values — it preserves the original stream while executing the provided callback safely for each emitted element.

iex> parent = self()
iex> stream =
...>   GRPC.Stream.from([1, 2, 3])
...>   |> GRPC.Stream.effect(fn x -> send(parent, {:seen, x * 2}) end)
...>   |> GRPC.Stream.to_flow()
...>   |> Enum.to_list()
iex> assert_receive {:seen, 2}
iex> assert_receive {:seen, 4}
iex> assert_receive {:seen, 6}
iex> stream
[1, 2, 3]

Key characteristics:

  • The callback function (effect_fun) is invoked for each item emitted downstream.
  • The result of the callback is ignored, ensuring that the stream’s structure and values remain unchanged.
  • Execution is lazy and occurs only when the stream is materialized using run/1, run_with/3, or to_flow/1.
  • Exceptions raised inside the callback are captured internally, preventing interruption of the dataflow.

This operator is designed for observability, telemetry, auditing, and integration with external systems that must react to events flowing through the gRPC stream.


Recovery from errors

The map_error/2 operator intercepts and transforms errors or exceptions emitted by previous stages in a stream pipeline.

It provides a unified mechanism for handling:

  • Expected errors, such as validation or domain failures ({:error, reason})
  • Unexpected runtime errors, including raised or thrown exceptions inside other operators.
iex> GRPC.Stream.from([1, 2])
...> |> GRPC.Stream.map(fn
...>   2 -> raise "boom"
...>   x -> x
...> end)
...> |> GRPC.Stream.map_error(fn
...>   {:error, {:exception, _reason}} ->
...>     {:error, GRPC.RPCError.exception(message: "Booomm")}
...> end)

In this example:

  • The function inside map/2 raises an exception for the value 2.
  • map_error/2 captures and transforms that error into a structured GRPC.RPCError response.
  • The stream continues processing without being interrupted.

This makes map_error/2 suitable for input validation, runtime fault recovery, and user-facing error translation within gRPC pipelines.


Unified Error Matching and Propagation

All stream operators share a unified error propagation model that guarantees consistent handling of exceptions and failures across the pipeline.

This ensures that user-defined functions within the stream — whether pure transformations, side effects, or external calls — always produce a predictable and recoverable result, maintaining the integrity of the dataflow even in the presence of unexpected errors.

def say_unary_hello(request, _materializer) do
  GRPCStream.unary(request)
  |> GRPCStream.ask(Transformer)
  |> GRPCStream.map(fn
    %HelloReply{} = reply ->
      %HelloReply{message: "[Reply] #{reply.message}"}

    {:error, reason} ->
      {:error, GRPC.RPCError.exception(message: "error calling external process: #{inspect(reason)}")}

    error ->
      Logger.error("Unknown error")
      error
  end)
  |> GRPCStream.run()
end

By normalizing all possible outcomes, GRPC.Stream ensures fault-tolerant, exception-safe pipelines where operators can freely raise, throw, or return tuples without breaking the flow execution.

This unified model allows developers to build composable and reliable streaming pipelines that gracefully recover from both domain and runtime errors.

NOTE: In the example above, we could use map_error/2 instead of map/2 to handle error cases explicitly. However, since the function also performs a transformation on successful values, map/2 remains appropriate and useful in this context.


Application Startup

Add the server supervisor to your application's supervision tree:

defmodule Helloworld.Application do
  @moduledoc false
  use Application

  @impl true
  def start(_type, _args) do
    children = [
      GrpcReflection,
      {
        GRPC.Server.Supervisor, [
          endpoint: Helloworld.Endpoint,
          port: 50051,
          start_server: true,
          # adapter_opts: [# any adapter-specific options like tls configuration....]
        ]
      }
    ]

    opts = [strategy: :one_for_one, name: Helloworld.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

Client Usage

This section demonstrates how to establish client connections and perform RPC calls using the Elixir gRPC client.


Basic Connection and RPC

Typically, you start this client supervisor as part of your application's supervision tree:

children = [
  {GRPC.Client.Supervisor, []}
]

opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)

You can also start it manually in scripts or test environments:

{:ok, _pid} = DynamicSupervisor.start_link(strategy: :one_for_one, name: GRPC.Client.Supervisor)

Then connect with gRPC server:

iex> {:ok, channel} = GRPC.Stub.connect("localhost:50051")
iex> request = Helloworld.HelloRequest.new(name: "grpc-elixir")
iex> {:ok, reply} = channel |> Helloworld.GreetingServer.Stub.say_unary_hello(request)

Using Interceptors

Client interceptors allow you to add logic to the request/response lifecycle, such as logging, tracing, or authentication.

iex> {:ok, channel} =
...>   GRPC.Stub.connect("localhost:50051",
...>     interceptors: [GRPC.Client.Interceptors.Logger]
...>   )
iex> request = Helloworld.HelloRequest.new(name: "Alice")
iex> {:ok, reply} = channel |> Helloworld.GreetingServer.Stub.say_unary_hello(request)

Target Schemes and Resolvers

The connect/2 function supports URI-like targets that are resolved via the internal gRPC Resolver. You can connect using DNS, Unix Domain sockets, IPv4/IPv6, or even xDS-based endpoints.

Supported formats:

SchemeExampleDescription
dns://"dns://example.com:50051"Resolves via DNS A/AAAA records
ipv4:"ipv4:10.0.0.5:50051"Connects directly to an IPv4 address
unix:"unix:/tmp/service.sock"Connects via a Unix domain socket
xds:///"xds:///my-service"Resolves via xDS control plane (Envoy/Istio)
none"127.0.0.1:50051"Implicit DNS (default port 50051)

Example (DNS):

iex> {:ok, channel} = GRPC.Stub.connect("dns://orders.prod.svc.cluster.local:50051")
iex> request = Orders.GetOrderRequest.new(id: "123")
iex> {:ok, reply} = channel |> Orders.OrderService.Stub.get_order(request)

Example (Unix socket):

iex> {:ok, channel} = GRPC.Stub.connect("unix:/tmp/my.sock")

NOTE: When using DNS or xDS targets, the connection layer periodically refreshes endpoints.


Compression and Metadata

You can specify message compression and attach default headers to all requests.

iex> {:ok, channel} =
...>   GRPC.Stub.connect("localhost:50051",
...>     compressor: GRPC.Compressor.Gzip,
...>     headers: [{"authorization", "Bearer my-token"}]
...>   )

Client Adapters

By default, GRPC.Stub.connect/2 uses the Gun adapter. You can switch to Mint (pure Elixir HTTP/2) or other adapters as needed.

Using Mint Adapter

iex> GRPC.Stub.connect("localhost:50051",
...>   adapter: GRPC.Client.Adapters.Mint
...> )

You can configure adapter options globally via your application’s config:

# File: config/config.exs
config :grpc, GRPC.Client.Adapters.Mint,
  timeout: 10_000,
  transport_opts: [cacertfile: "/etc/ssl/certs/ca-certificates.crt"]

The accepted options are the same as Mint.HTTP.connect/4.


HTTP Transcoding

  1. Adding grpc-gateway annotations to your protobuf file definition:
import "google/api/annotations.proto";
import "google/protobuf/timestamp.proto";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {
    option (google.api.http) = {
      get: "/v1/greeter/{name}"
    };
  }

  rpc SayHelloFrom (HelloRequestFrom) returns (HelloReply) {
    option (google.api.http) = {
      post: "/v1/greeter"
      body: "*"
    };
  }
}
  1. Add protoc plugin dependency and compile your protos using protobuf_generate hex package:

In mix.exs:

def deps do
  [
    {:grpc, "~> 0.11"},
    {:protobuf_generate, "~> 0.1.3"}
  ]
end

And in your terminal:

mix protobuf.generate \
  --include-path=priv/proto \
  --include-path=deps/googleapis \
  --generate-descriptors=true \
  --output-path=./lib \
  --plugins=ProtobufGenerate.Plugins.GRPCWithOptions \
  google/api/annotations.proto google/api/http.proto helloworld.proto
  1. Enable http_transcode option in your Server module
defmodule Helloworld.Greeter.Server do
  use GRPC.Server,
    service: Helloworld.Greeter.Service,
    http_transcode: true

  # callback implementations...
end

See full application code in helloworld_transcoding example.

CORS

When accessing gRPC from a browser via HTTP transcoding or gRPC-Web, CORS headers may be required for the browser to allow access to the gRPC endpoint. Adding CORS headers can be done by using GRPC.Server.Interceptors.CORS as an interceptor in your GRPC.Endpoint module, configuring it as described in the module documentation:

Example:

# Define your endpoint
defmodule Helloworld.Endpoint do
  use GRPC.Endpoint

  intercept GRPC.Server.Interceptors.Logger
  intercept GRPC.Server.Interceptors.CORS, allow_origin: "mydomain.io"
  run Helloworld.Greeter.Server
end

Features

Benchmark

  1. Simple benchmark by using ghz

  2. Benchmark followed by official spec

Contributing

Your contributions are welcome!

Please open issues if you have questions, problems and ideas. You can create pull requests directly if you want to fix little bugs, add small features and so on. But you'd better use issues first if you want to add a big feature or change a lot of code.