Use Cases (Updated: 6/3/2026)

Claude Code gRPC Development: Protobuf, Streaming, Deadlines, Auth

Build a runnable gRPC service with Claude Code, Protobuf, deadlines, streaming, auth, and observability.

Claude Code gRPC Development: Protobuf, Streaming, Deadlines, Auth

Start With The Contract, Not The Server

gRPC is a remote procedure call framework: a client calls a method on another service as if it were a local function, while the real work happens over the network. Protocol Buffers define the contract: service names, methods, request messages, response messages, and field numbers. That contract is the part Claude Code must respect before it writes server or client code.

Use the official docs as the source of truth while working: gRPC Introduction, Core concepts, Node basics, Deadlines, Status Codes, Authentication, OpenTelemetry Metrics, and the Protocol Buffers proto3 guide.

For adjacent ClaudeCodeLab material, pair this with production API development, microservices with Claude Code, API versioning strategy, and testing strategies.

Practical Use Cases

Do not choose gRPC only because it sounds fast. Choose it when a typed contract and clear failure behavior help the system.

Use caseWhy gRPC fitsWhat to ask Claude Code to do
Internal order, inventory, billing servicesThe contract catches drift between teamsDraft .proto, server, client, and status-code mapping
Large exports or feedsServer streaming avoids one huge responseImplement returns (stream Item) plus cancellation handling
Mixed Go, Node, Python servicesOne schema can drive several language clientsDocument generation commands and CI checks
Internal AI tools and agentsExplicit methods reduce ambiguous tool callsAdd sample clients, metadata auth, deadlines, and logs

Masa’s practical lesson from small service work is that a single broad Search method looks convenient but becomes hard to evolve. Split read, create, and streaming workflows early, then let Claude Code update each file against that contract.

Runnable Node gRPC Example

This sample uses @grpc/proto-loader, so it does not require protoc for the first local proof. Create a directory and install the dependencies:

mkdir claude-grpc-demo
cd claude-grpc-demo
npm init -y
npm install @grpc/grpc-js @grpc/proto-loader
mkdir proto

Use this package.json:

{
  "type": "commonjs",
  "scripts": {
    "server": "node server.js",
    "client": "node client.js"
  },
  "dependencies": {
    "@grpc/grpc-js": "latest",
    "@grpc/proto-loader": "latest"
  }
}

Create proto/task.proto:

syntax = "proto3";

package tasks.v1;

service TaskService {
  rpc CreateTask(CreateTaskRequest) returns (Task);
  rpc GetTask(GetTaskRequest) returns (Task);
  rpc ListTasks(ListTasksRequest) returns (stream Task);
}

message Task {
  string id = 1;
  string title = 2;
  string status = 3;
  int64 created_at_unix = 4;
}

message CreateTaskRequest {
  string title = 1;
}

message GetTaskRequest {
  string id = 1;
}

message ListTasksRequest {
  int32 limit = 1;
}

Create server.js:

const path = require("node:path");
const { randomUUID } = require("node:crypto");
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");

const PROTO_PATH = path.join(__dirname, "proto", "task.proto");
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
  keepCase: false,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});
const taskProto = grpc.loadPackageDefinition(packageDefinition).tasks.v1;
const token = process.env.DEMO_TOKEN || "dev-token";
const tasks = new Map();

function grpcError(code, message) {
  const error = new Error(message);
  error.code = code;
  return error;
}

function assertAuthenticated(call) {
  const value = call.metadata.get("authorization")[0];
  if (value !== `Bearer ${token}`) {
    throw grpcError(grpc.status.UNAUTHENTICATED, "UNAUTHENTICATED");
  }
}

function createTask(call, callback) {
  try {
    assertAuthenticated(call);
    const title = String(call.request.title || "").trim();
    if (!title) {
      return callback(grpcError(grpc.status.INVALID_ARGUMENT, "INVALID_ARGUMENT: title"));
    }

    const task = {
      id: randomUUID(),
      title,
      status: "OPEN",
      createdAtUnix: String(Math.floor(Date.now() / 1000)),
    };
    tasks.set(task.id, task);
    callback(null, task);
  } catch (error) {
    callback(error);
  }
}

function getTask(call, callback) {
  try {
    assertAuthenticated(call);
    const task = tasks.get(call.request.id);
    if (!task) {
      return callback(grpcError(grpc.status.NOT_FOUND, "NOT_FOUND: task"));
    }
    callback(null, task);
  } catch (error) {
    callback(error);
  }
}

function listTasks(call) {
  try {
    assertAuthenticated(call);
    const limit = Math.min(Math.max(Number(call.request.limit) || 10, 1), 100);
    for (const task of Array.from(tasks.values()).slice(0, limit)) {
      call.write(task);
    }
    call.end();
  } catch (error) {
    call.destroy(error);
  }
}

const server = new grpc.Server();
server.addService(taskProto.TaskService.service, { createTask, getTask, listTasks });
server.bindAsync("127.0.0.1:50051", grpc.ServerCredentials.createInsecure(), (error, port) => {
  if (error) throw error;
  console.log(`TaskService listening on ${port}`);
});

Create client.js:

const path = require("node:path");
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");

const PROTO_PATH = path.join(__dirname, "proto", "task.proto");
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
  keepCase: false,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});
const taskProto = grpc.loadPackageDefinition(packageDefinition).tasks.v1;
const client = new taskProto.TaskService("127.0.0.1:50051", grpc.credentials.createInsecure());
const metadata = new grpc.Metadata();
metadata.set("authorization", `Bearer ${process.env.DEMO_TOKEN || "dev-token"}`);

function deadline(ms) {
  return new Date(Date.now() + ms);
}

function createTask(title) {
  return new Promise((resolve, reject) => {
    client.createTask({ title }, metadata, { deadline: deadline(1000) }, (error, task) => {
      if (error) return reject(error);
      resolve(task);
    });
  });
}

function getTask(id) {
  return new Promise((resolve, reject) => {
    client.getTask({ id }, metadata, { deadline: deadline(1000) }, (error, task) => {
      if (error) return reject(error);
      resolve(task);
    });
  });
}

function listTasks(limit) {
  return new Promise((resolve, reject) => {
    const rows = [];
    const stream = client.listTasks({ limit }, metadata, { deadline: deadline(1000) });
    stream.on("data", (task) => rows.push(task));
    stream.on("error", reject);
    stream.on("end", () => resolve(rows));
  });
}

async function main() {
  const created = await createTask("Claude Code gRPC");
  const fetched = await getTask(created.id);
  const rows = await listTasks(10);
  console.log(JSON.stringify({ created, fetched, streamed: rows.length }, null, 2));
  client.close();
}

main().catch((error) => {
  console.error(error.code, error.details || error.message);
  client.close();
  process.exitCode = 1;
});

Run the server in one terminal:

npm run server

Run the client in another:

npm run client

Evolution, Security, And Failure Modes

Schema evolution is where thin gRPC articles usually fail. Never reuse field numbers. If a field is deleted, reserve its number and name. Add new fields with new numbers, prefer explicit presence when the client must distinguish “unset” from “empty”, and keep enum value 0 for an unspecified state.

message Task {
  string id = 1;
  string title = 2;
  string status = 3;
  int64 created_at_unix = 4;
  optional string assignee_email = 5;
  reserved 6, 7;
  reserved "owner_email";
}

Deadlines are not optional discipline. The gRPC docs note that clients can otherwise wait indefinitely, so every client call should set a realistic deadline. Streaming is useful for exports and live progress, but it must handle cancellation and avoid buffering the whole result in memory.

The local sample uses createInsecure() only to stay copyable. Production traffic should use TLS with grpc.ServerCredentials.createSsl(...) on the server and grpc.credentials.createSsl(rootCert) on the client. Bearer tokens in metadata are not enough if the channel is not encrypted.

Observability should include method, status, duration, cancellation, deadline expiry, and unavailable backends. OpenTelemetry gRPC metrics are a good baseline because they expose per-call client and server measurements. For failures, avoid returning everything as UNKNOWN; use INVALID_ARGUMENT, NOT_FOUND, UNAUTHENTICATED, DEADLINE_EXCEEDED, UNAVAILABLE, and RESOURCE_EXHAUSTED intentionally. Retry only idempotent calls unless you have an idempotency key.

Prompt Claude Code With Review Criteria

Use a scoped implementation prompt:

Implement TaskService with gRPC.
Constraints:
- Create proto/task.proto first.
- Use Node.js, @grpc/grpc-js, and @grpc/proto-loader.
- Implement CreateTask, GetTask, and ListTasks as server streaming.
- Add client deadlines to every RPC.
- Validate authorization metadata.
- Mention that production must replace createInsecure with TLS.
- Run npm run client and report the output.
Editable files:
- proto/task.proto
- server.js
- client.js
- package.json

Then ask for a critical review:

Review this gRPC implementation findings first.
Check schema evolution, field-number reuse, deadlines, cancellation,
status codes, streaming memory behavior, TLS/auth, and observability.
Rank issues by severity and include exact file references.

CTA And Verification Result

If you are learning alone, start with the free Claude Code cheatsheet and adapt this sample to a throwaway service. Teams that need .proto governance, CI gates, review prompts, TLS rollout, and observability can use Claude Code training and consultation. For reusable setup material, see the ClaudeCodeLab products.

For this refresh, the sample was actually run in a temporary directory with Node v24.14.1 and npm 11.11.0: npm install, node server.js, and node client.js completed, and the client printed created, fetched, and streamed: 1. Go, protoc, and the Go protobuf plugins were not available on this machine, so the article intentionally uses the Node dynamic-loading path that could be verified here.

#Claude Code #gRPC #Protocol Buffers #microservices #backend
Free

Free PDF: Claude Code Cheatsheet

Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.

We handle your data with care and never send spam.

Level up your Claude Code workflow

Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.