Walkthrough: gRPC Greeter
Sample: examples/grpc_greeter · Needs: nothing but Dart (a gRPC client to call it)
A minimal gRPC service — one unary method, SayHello — running alongside the normal Vania HTTP server. gRPC is a high-performance RPC protocol built on Protocol Buffers; this sample shows how to stand a service up with vania_grpc and, as a bonus, demystifies what protobuf encoding actually is.
See the gRPC package page for the full API.
The service definition
In gRPC terms:
service greeter.Greeter {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest { string name = 1; }
message HelloResponse { string message = 1; }
Run it
cd examples/grpc_greeter
dart pub get
dart run bin/server.dart
The gRPC server listens on GRPC_PORT (default 50051); the HTTP server also comes up on APP_PORT. Call it with any gRPC client, for example grpcurl:
grpcurl -plaintext -d '{"name":"Vania"}' \
localhost:50051 greeter.Greeter/SayHello
# { "message": "Hello, Vania!" }
(Reflection isn't enabled, so the method name is passed explicitly.)
Registering the service
// lib/config/app.dart
'providers': <ServiceProvider>[
GrpcServiceProvider(
services: [GreeterService()],
config: GrpcConfig(
host: env('GRPC_HOST', '0.0.0.0'),
port: env<int>('GRPC_PORT', 50051),
autoStart: true, // start the gRPC server when the app boots
),
),
],
services: is the list of gRPC services to expose; autoStart: true brings the server up automatically at boot.
The service: rule as a pure function, plus a handler
The greeting rule is a plain function, kept separate so it can be tested without a gRPC call:
// lib/greeter/greeter_service.dart
String greetingFor(String name) =>
'Hello, ${name.trim().isEmpty ? 'World' : name.trim()}!';
The service registers the SayHello method and delegates to that function:
class GreeterService extends Service {
GreeterService() {
$addMethod(ServiceMethod<HelloRequest, HelloResponse>(
'SayHello',
sayHello,
false, // client streaming?
false, // server streaming?
HelloRequest.fromBuffer, // how to decode the request
(response) => response.writeToBuffer(),// how to encode the response
));
}
@override
String get $name => 'greeter.Greeter';
Future<HelloResponse> sayHello(ServiceCall call, Future<HelloRequest> request) async {
final req = await request;
return HelloResponse(message: greetingFor(req.name));
}
}
The two false flags say this is a unary call — one request, one response, no streaming. The two function arguments tell gRPC how to turn bytes into a HelloRequest and a HelloResponse back into bytes. In a real project those (de)serializers come from generated code.
What protobuf actually is (the honest bit)
Normally you write a .proto file and run protoc to generate the message classes. To keep the sample dependency-free, it hand-writes the wire encoding for a message with a single string field:
// lib/greeter/hello_messages.dart
List<int> _encodeStringField1(String value) {
final bytes = utf8.encode(value);
return [0x0A, ..._writeVarint(bytes.length), ...bytes]; // tag 1, wire type 2
}
That 0x0A is not magic: it is field number 1, wire type 2 (length-delimited), packed into one byte (1 << 3 | 2). Then comes the length as a varint, then the UTF-8 bytes. The decoder reverses it. You will almost never write this by hand — but seeing it once makes protobuf feel a lot less like a black box: it is just tagged, length-prefixed fields.
class HelloRequest {
const HelloRequest({this.name = ''});
final String name;
List<int> writeToBuffer() => _encodeStringField1(name);
static HelloRequest fromBuffer(List<int> bytes) =>
HelloRequest(name: _decodeStringField1(bytes));
}
In a real service, replace hello_messages.dart with protoc-generated classes and nothing else about the service changes.
Testing without a server
Everything is testable in-process: the message codec round-trips (encode then decode returns the original), greetingFor is a pure function, and sayHello can be invoked directly with a Future<HelloRequest>:
dart test
What to take away
- A gRPC service is registered through
GrpcServiceProvider;autoStartruns it on boot, next to your HTTP server. - Keep the actual rule (
greetingFor) a pure function; the service method is a thin wrapper. - Protobuf messages are tagged, length-prefixed fields — normally generated by
protoc, hand-written here only to avoid codegen. - Unary vs streaming is set by the two boolean flags on the method.