QA Interview Hub · Knowledge base · Quizzes
API
gRPC
gRPC: what it is and how it differs from REST
gRPC is a remote procedure call (RPC) framework from Google: a client calls a service method as if it were a local function. The contract is described in a .proto file, and client and server stubs are generated from it for many languages. The transport is HTTP/2, the default message format is Protocol Buffers (binary, compact, with a strict schema).
syntax = "proto3";
package shop.v1;
service OrderService {
rpc GetOrder (GetOrderRequest) returns (Order);
rpc WatchOrders (WatchRequest) returns (stream Order);
}
message GetOrderRequest { string id = 1; }
message Order {
string id = 1;
int64 total_cents = 2;
Status status = 3;
}
enum Status { STATUS_UNSPECIFIED = 0; PAID = 1; SHIPPED = 2; }
Compared with REST and GraphQL:
- Contract. gRPC is contract-first, the schema is mandatory and typed; in REST the schema (OpenAPI) is optional and often drifts from the implementation;
- Model. gRPC calls methods (
GetOrder), REST works with resources and HTTP verbs, GraphQL sends a free-form query to a single endpoint;
- Performance. The binary format and HTTP/2 multiplexing give a smaller size and lower latency than JSON over HTTP/1.1;
- Streaming. gRPC supports it out of the box; in REST it takes separate solutions (SSE, WebSocket);
- Browser. gRPC does not work in a browser directly (grpc-web or Connect and a proxy are needed), while REST works everywhere;
- Debugging. JSON can be viewed in a browser or with
curl; binary gRPC needs the schema and tools (grpcurl, Postman).
Where it is used: service-to-service calls in microservices, low latency, streaming, mobile clients, polyglot teams. Public APIs for third-party developers more often stay REST.
Protobuf: schema compatibility rules you must not break
In a binary message a field is identified by its number, not its name. So the number is part of the contract: clients and servers of different versions run side by side, and the schema must stay compatible both ways (backward: new servers read old messages; forward: old ones read new messages).
Safe changes:
- add a new field with a new number (old clients ignore it, new ones get the default value from old servers);
- add a new enum value, a new method or a new service;
- rename a field: nothing changes in the binary format, but the JSON representation and clients' source code break, so check that separately.
Dangerous changes:
- change a field number or reuse the number of a removed field: old data is read as a completely different field;
- change a type to one that is incompatible on the wire (for example
string to int32); some integer types (int32, uint32, int64, bool) are mutually compatible, but values get truncated;
- switch between
repeated and a scalar field, or change a oneof so that a field enters or leaves the group;
- remove a field without
reserved.
message User {
reserved 4, 7;
reserved "legacy_phone";
string id = 1;
string email = 2;
optional string nickname = 3;
}
Default values. In proto3 a scalar field with a zero value (0, empty string, false) is not sent, and the receiver cannot tell "not set" from "0". If the difference matters (for example "discount 0%" versus "discount not specified"), use optional (there is a presence flag, hasX) or wrappers like google.protobuf.Int32Value. The first enum value must be zero and is usually named *_UNSPECIFIED.
Unknown fields (added by a newer schema and unknown to old code) are kept and passed on by modern implementations rather than lost. Numbers 1–15 take one byte of tag, so they are kept for the most frequent fields.
Call types, status codes, deadlines and metadata
Four RPC types (HTTP/2 allows many streams on one connection):
- Unary: one request, one response (the most common, closest to REST);
- Server streaming: one request, a stream of responses (an event feed, large result sets);
- Client streaming: a stream of requests, one response at the end (file upload, a batch of metrics);
- Bidirectional streaming: both sides send streams independently (chat, interactive sessions).
Statuses. Whatever the outcome, the HTTP status of the response is 200, and the real result arrives in the trailers as grpc-status (a number) and grpc-message. So you test the gRPC code, not the HTTP one. The main ones:
OK (0); CANCELLED (1); UNKNOWN (2);
INVALID_ARGUMENT (3): the request is invalid on its merits (≈ HTTP 400);
DEADLINE_EXCEEDED (4): the deadline ran out (≈ 504);
NOT_FOUND (5), ALREADY_EXISTS (6);
PERMISSION_DENIED (7): authenticated but not allowed (≈ 403);
RESOURCE_EXHAUSTED (8): limits, quotas, a message that is too large (≈ 429);
FAILED_PRECONDITION (9): the system state does not allow the operation;
UNIMPLEMENTED (12): the method is not implemented; INTERNAL (13): a server error;
UNAVAILABLE (14): the service is temporarily unavailable, safe to retry (≈ 503);
UNAUTHENTICATED (16): no valid credentials (≈ 401).
Error details (for example which fields are invalid) travel as a google.rpc.Status structure with typed details (BadRequest, ErrorInfo) in the grpc-status-details-bin header.
Metadata is the analogue of HTTP headers: key-value pairs at the start (headers) and at the end of a call (trailers). Keys are lowercase, binary ones end with -bin, the grpc- prefix is reserved. Metadata carries a token (authorization: Bearer …), a trace id, the locale.
A deadline is an absolute time up to which the client is ready to wait; on the wire it is sent as grpc-timeout. By default there is no deadline, so a call can hang forever: production clients always set one. The deadline should propagate down the chain of services, and once it expires the server must stop working. Retries are configured in the service config (retryPolicy: attempts, backoff, the list of retryable statuses, usually UNAVAILABLE); only idempotent calls are worth retrying. Interceptors (middleware) wrap calls on the client and the server: logging, authorization, metrics, tracing.
How a QA engineer tests a gRPC service
Tools.
- grpcurl: "curl for gRPC". If server reflection is enabled on the server, the schema comes from it; otherwise pass
-proto and -import-path. Metadata: -H;
- Postman / Insomnia: gRPC requests with a
.proto import or via reflection, handy for manual exploration and demos;
- A generated client in the test framework. For TypeScript:
@grpc/grpc-js with @grpc/proto-loader, or typed code from ts-proto, Buf/Connect. This is the main option for automated tests: types catch drift from the schema at compile time;
- buf:
buf lint and buf breaking --against '.git#branch=main' in CI show whether a PR broke backward compatibility of the schema;
- Load:
ghz or k6 (the k6/net/grpc module).
# list services and describe one (needs reflection)
grpcurl -plaintext localhost:50051 list
grpcurl -plaintext localhost:50051 describe shop.v1.OrderService
# a call with metadata
grpcurl -plaintext -H 'authorization: Bearer TOKEN' \
-d '{"id": "42"}' localhost:50051 shop.v1.OrderService/GetOrder
const deadline = new Date(Date.now() + 2000);
client.getOrder({ id: '42' }, meta, { deadline }, (err, order) => {
expect(err?.code).toBe(grpc.status.NOT_FOUND);
});
What to check:
- Positive scenarios and the values of all response fields, including defaults and
optional;
- Validation: empty/oversized/invalid fields give
INVALID_ARGUMENT with correct details, not INTERNAL or UNKNOWN;
- Error codes:
NOT_FOUND, ALREADY_EXISTS, PERMISSION_DENIED versus UNAUTHENTICATED (a missing, expired or someone else's token);
- Deadlines: a slow dependency gives
DEADLINE_EXCEEDED, and the server does not carry on working after cancellation;
- Streaming: message order, stream completion, cancellation by the client, a mid-stream break, backpressure, an empty stream;
- Large messages: the size limit (the default receive limit is usually 4 MB) gives
RESOURCE_EXHAUSTED rather than a server crash;
- Compatibility: an old client with a fresh server and vice versa, unknown fields and enum values;
- Idempotency and retries: a repeated call after
UNAVAILABLE creates no duplicates;
- Security: TLS/mTLS, method-level authorization, reflection disabled in production (or protected by authentication).
Browser and gateways. Browser JS does not control HTTP/2 frames and trailers, so direct gRPC is unavailable. The solutions: grpc-web through a proxy (for example Envoy), the Connect protocol, or gRPC-Gateway, which exposes REST/JSON over a gRPC service. Tests should be run against the gateway too, because it has its own mapping of statuses to HTTP codes and its own JSON serialization.