gRPC Cheatsheet
Protocol Buffers
Use this gRPC reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
File Structure
syntax = "proto3"; // always first non-comment line package myapp.v1; // namespaces generated code option go_package = "github.com/me/myapp/gen/myapp/v1;myappv1"; option java_multiple_files = true; option java_package = "com.myapp.v1"; import "google/protobuf/timestamp.proto"; // well-known types import "other.proto"; // relative or on include path
Scalar Field Types
| Proto type | JS type (proto-loader) | Notes |
|---|---|---|
double | number | 64-bit float |
float | number | 32-bit float |
int32 | number | variable-length encoding |
int64 | string | number | set longs: String to avoid precision loss |
uint32 | number | unsigned 32-bit |
uint64 | string | number | same caution as int64 |
sint32 | number | ZigZag — efficient for negatives |
sint64 | string | number | ZigZag 64-bit |
fixed32 | number | always 4 bytes |
fixed64 | string | number | always 8 bytes |
sfixed32 | number | signed fixed 32 |
sfixed64 | string | number | signed fixed 64 |
bool | boolean | |
string | string | UTF-8 |
bytes | Buffer / Uint8Array | arbitrary binary |
Message Definition
message User {
// Field number 1–15 cost 1 byte, 16–2047 cost 2 bytes.
// Reserve low numbers for frequently-set fields.
int32 id = 1;
string name = 2;
string email = 3;
bool is_active = 4;
repeated string tags = 5; // repeated = array/list
}Nested Messages
message Address {
string street = 1;
string city = 2;
string zip = 3;
}
message User {
int32 id = 1;
string name = 2;
Address address = 3; // embedded sub-message
}Oneof
message SearchRequest {
oneof query {
string keyword = 1;
int32 user_id = 2;
bytes binary = 3;
}
int32 page = 4;
}Only one field in a
oneofcan be set at a time. Setting a second field clears the first.
Map Fields
message Config {
map<string, string> settings = 1; // key must be scalar (not float/bytes/message)
map<string, User> members = 2; // value can be a message
}Enums
enum Status {
STATUS_UNSPECIFIED = 0; // proto3: first value MUST be 0
STATUS_ACTIVE = 1;
STATUS_INACTIVE = 2;
STATUS_PENDING = 3;
}
message User {
Status status = 1;
}Enum Aliases
enum Direction {
option allow_alias = true;
DIRECTION_UNSPECIFIED = 0;
DIRECTION_LEFT = 1;
LEFT = 1; // alias — same number, different name
DIRECTION_RIGHT = 2;
}Reserved Fields
message OldMessage {
reserved 2, 15, 9 to 11; // reserved field numbers
reserved "foo", "bar"; // reserved field names
}Use
reservedto prevent re-use of deleted field numbers or names, which would cause silent data corruption during deserialization.
Well-Known Types
import "google/protobuf/timestamp.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/any.proto"; import "google/protobuf/wrappers.proto"; import "google/protobuf/empty.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/field_mask.proto"; message Event { google.protobuf.Timestamp created_at = 1; // UTC instant google.protobuf.Duration ttl = 2; // duration google.protobuf.Any payload = 3; // arbitrary message google.protobuf.StringValue name = 4; // nullable string (wrapper) } // Empty — use instead of defining a no-field message rpc Ping(google.protobuf.Empty) returns (google.protobuf.Empty);
Timestamp in Node.js
const { Timestamp } = require('google-protobuf/google/protobuf/timestamp_pb'); // Create const ts = new Timestamp(); ts.fromDate(new Date()); // Read const date = ts.toDate(); // With proto-loader (plain object) const ts = { seconds: Math.floor(Date.now() / 1000), nanos: 0 };
Field Options
message User {
string email = 1 [deprecated = true]; // signal: do not use
int32 id = 2 [(validate.rules).int32.gt = 0]; // custom option (protoc-gen-validate)
}Extensions & Any
// Pack/unpack Any in JS (static codegen) const { Any } = require('google-protobuf/google/protobuf/any_pb'); const userMsg = new UserMessage(); userMsg.setName('Alice'); const packed = new Any(); packed.pack(userMsg.serializeBinary(), 'myapp.v1.UserMessage'); // Unpack const unpacked = packed.unpack(UserMessage.deserializeBinary, 'myapp.v1.UserMessage');
Versioning and Evolution Rules
| Safe change | Unsafe change (breaks wire compat) |
|---|---|
| Add a new field (new number) | Change a field number |
| Remove a field (reserve the number) | Change a field type incompatibly |
| Rename a field (number stays) | Reuse a deleted field number |
| Add an enum value | Remove an enum value in use |
Change optional → repeated | Change repeated → singular |
Proto3 defaults — every scalar field has a zero default (
0,"",false). There is no way to distinguish "field not set" from "field set to zero" for scalars. Use wrapper types (google.protobuf.Int32Value) oroneofif you need null/absent semantics.
Compiling with protoc
# Install protoc brew install protobuf # macOS apt-get install -y protobuf-compiler # Debian/Ubuntu # Node.js: dynamic loading (no codegen needed) # Just use @grpc/proto-loader at runtime. # Node.js: static codegen npm install -g grpc-tools grpc_tools_node_protoc \ --js_out=import_style=commonjs,binary:./gen \ --grpc_out=grpc_js:./gen \ --proto_path=./protos \ ./protos/myservice.proto # Go protoc --go_out=. --go-grpc_out=. ./protos/myservice.proto # Python python -m grpc_tools.protoc -I./protos \ --python_out=. --grpc_python_out=. ./protos/myservice.proto
Proto Loader Options Reference
protoLoader.loadSync('service.proto', { keepCase: true, // false = camelCase field names (proto3 default naming) longs: String, // 'String' | 'Number' | Long (npm long package) enums: String, // 'String' | Number bytes: Buffer, // Buffer | Array | String defaults: true, // include default-valued fields in output objects arrays: false, // repeated fields always as arrays even if empty objects: false, // map fields always as objects even if empty oneofs: true, // add virtual oneof field indicating which field is set json: false, // interpret bytes as base64 JSON strings includeDirs: ['./protos', './third_party'], // additional include paths });