xx
.md

How xxRPC is meant to be used

xxRPC connects programs written in different languages through a shared Protobuf schema. The schema owns the complete application contract: service names, procedure names, request data, response data, and expected errors.

HTTP and WebSocket carry xxRPC messages. They do not define the application contract. Application code must not depend on untyped HTTP headers, cookies, status codes, or other transport details that are absent from the Protobuf schema.

This separation lets the same service support browsers, command-line tools, backend programs, and other clients without changing its contract for one environment.

The Protobuf schema owns the contract

If a value can change what a procedure does, put that value in the schema. Examples include:

Do not pass these values through custom HTTP headers. A header is not part of a generated procedure type, so a caller can omit it while still satisfying the apparent contract. The server can also start depending on a header without causing clients in other languages to fail at compile time.

xxRPC uses transport metadata internally to route and deliver calls. Generated clients and handlers own that metadata. Application code must not use transport metadata as an undeclared extension to a procedure.

Server resources do not belong in the schema. Databases, secrets, loggers, and platform bindings are implementation dependencies. Pass them to the handler implementation through constructors or other internal module interfaces.

Errors are procedure results

Expected errors are part of a procedure's response schema. Model success and error cases with a Protobuf oneof:

service Tasks {
  rpc CreateTask(CreateTaskRequest) returns (CreateTaskResponse);
}

message CreateTaskRequest {
  string access_token = 1;
  string organization_id = 2;
  string title = 3;
}

message CreateTaskResponse {
  oneof result {
    Task task = 1;
    CreateTaskError error = 2;
  }
}

message CreateTaskError {
  oneof kind {
    Unauthenticated unauthenticated = 1;
    PermissionDenied permission_denied = 2;
    OrganizationNotFound organization_not_found = 3;
    InvalidTask invalid_task = 4;
  }
}

The handler returns a CreateTaskResponse for each expected outcome. It does not throw an exception for invalid credentials, denied access, missing data, conflicts, or business-rule failures. Generated clients receive a typed response and must handle its result variant.

Protobuf permits a oneof to be unset. Handlers must set every result and error oneof. Clients must treat an unset oneof as a contract violation, not as another application outcome.

Use exceptions only when the implementation cannot produce a valid contract response. Examples include a programming error or a failure to encode the response. Such failures mean that the server did not honor the procedure contract.

For streaming procedures, put expected errors in the stream's message schema. Reserve xxRPC protocol errors for failures that prevent the call from producing its declared messages.

HTTP only transports the call

An HTTP response status describes whether HTTP and xxRPC delivered a valid procedure answer. It does not describe whether the requested business operation succeeded.

Use these semantics:

SituationHTTP statusResponse
The server cannot identify the service or procedure.404No procedure answer exists.
The request body is not valid Protobuf.400The procedure could not receive its request.
The request exceeds the protocol limit.413The procedure could not receive its request.
The procedure returns a success variant.200A valid schema-defined answer.
The procedure returns an error variant.200A valid schema-defined answer.
The implementation cannot produce a valid answer.500The procedure contract was not honored.

A semantically invalid value is still a decoded request. For example, an empty title or an expired credential must produce a schema-defined error with HTTP 200. HTTP 400 is for a message that xxRPC cannot deliver to the procedure as a valid Protobuf request.

Do not map application errors to HTTP status codes. An HTTP 403, 404, or 409 cannot describe a procedure's declared error variants across every xxRPC transport and language.

Browsers are one kind of client

xxRPC supports browsers through standard browser HTTP and WebSocket facilities. Browser support does not make xxRPC a browser framework.

Cookies are useful for browser-specific authentication, but most xxRPC clients do not have a browser cookie jar. An xxRPC procedure must therefore not require an ambient cookie. Put the RPC credential in the schema. Keep operations that depend on an HttpOnly cookie in a separate browser-specific HTTP interface.

The same rule applies to HTTP-specific behavior such as redirects, form submissions, and cache controls. Keep that behavior in an HTTP endpoint unless it belongs in the cross-language RPC contract.

Standard gRPC does not run directly in browsers without another protocol or proxy such as gRPC-Web. tRPC gives TypeScript applications a good in-language experience, but its contract is not a practical shared interface for clients written in other languages. OpenAPI generation does not turn tRPC's TypeScript types and runtime behavior into one authoritative cross-language contract.

xxRPC takes a narrower approach. Protobuf defines the contract once. xxRPC generates clients and handlers for the supported languages and carries those messages over HTTP or WebSocket.

Not every operation is RPC

Use regular HTTP when HTTP itself provides the useful semantics or when an operation does not fit a procedure message. Common examples include:

A profile picture upload is an HTTP upload endpoint, not an RPC procedure with a hidden raw request body. The upload can return an identifier that later RPC calls use through their schemas.

Using both HTTP endpoints and xxRPC in one application is expected. Keep their interfaces separate. An HTTP endpoint can use headers, cookies, status codes, and streaming body semantics because those details are its declared interface. An xxRPC procedure uses only its Protobuf contract and xxRPC protocol behavior.

Design checklist

Before adding or changing an xxRPC procedure, check the following rules:

If a generated client can satisfy the procedure type while omitting information that the server requires, the schema is incomplete.