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 typeJS type (proto-loader)Notes
doublenumber64-bit float
floatnumber32-bit float
int32numbervariable-length encoding
int64string | numberset longs: String to avoid precision loss
uint32numberunsigned 32-bit
uint64string | numbersame caution as int64
sint32numberZigZag — efficient for negatives
sint64string | numberZigZag 64-bit
fixed32numberalways 4 bytes
fixed64string | numberalways 8 bytes
sfixed32numbersigned fixed 32
sfixed64string | numbersigned fixed 64
boolboolean
stringstringUTF-8
bytesBuffer / Uint8Arrayarbitrary 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 oneof can 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 reserved to 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 changeUnsafe 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 valueRemove an enum value in use
Change optionalrepeatedChange 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) or oneof if 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
});