Unary RPC
rpc Method(Request) returns (Response)Unary RPC is the simplest pattern: the client sends one request, the server sends one response. It behaves like a synchronous function call over a persistent HTTP/2 stream. Unary RPC is the right choice for most operations – CRUD, lookups, and any request-response interaction. The server may return a gRPC status code in trailers if processing fails.
Details
Unary RPC maps directly to the mental model of a function call: client calls → server processes → server responds
HTTP/2 mechanics: the client opens a new HTTP/2 stream (DATA frame with request), the server sends a response DATA frame and then HEADERS frame (trailers) containing Grpc-Status.
Error handling: if the server returns an error, it sets Grpc-Status to a non-zero code (1–16) and an optional Grpc-Message in the trailers. HTTP status is always 200 – gRPC status lives in trailers, not HTTP status.
Deadlines: every unary call should set a deadline (context with timeout). Without a deadline, a hung server blocks the caller indefinitely. The client cancels via RST_STREAM if the deadline expires.
When to use unary: - Any CRUD operation (create user, get order, update settings) - Lookup by ID - Synchronous validation - Authentication/authorization checks - ~80% of real-world gRPC methods are unary
Proto definition
syntax = "proto3";
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc CreateUser(CreateUserRequest) returns (User);
rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty);
}
message GetUserRequest { string user_id = 1; }
message CreateUserRequest {
string name = 1;
string email = 2;
}
message DeleteUserRequest { string user_id = 1; }Go server + client
// Server (Go)
func (s *UserService) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
user, err := s.db.GetUser(ctx, req.UserId)
if err != nil {
return nil, status.Errorf(codes.NotFound, "user %s not found", req.UserId)
}
return user, nil
}
// Client (Go) – set deadline
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
user, err := client.GetUser(ctx, &pb.GetUserRequest{UserId: "usr_123"})
if err != nil {
st, _ := status.FromError(err)
log.Printf("code=%s msg=%s", st.Code(), st.Message())
}When to use
CRUD operations
Lookups by ID
Validation endpoints
Auth checks
Any synchronous request-response
Backpressure
N/A – single request, single response. HTTP/2 flow control applies at the connection level.