← All posts

Proto-First Development: From .proto File to Running Service in 10 Minutes

A step-by-step tutorial showing how to go from an empty directory to a fully deployed microservice using Riven's proto-first workflow and unified CLI.

Gal Hindi

April 3, 2026 · 4 min read

Updated May 15, 2026

Most teams spend days — sometimes weeks — standing up a new service. You write boilerplate, wrestle with build configs, hand-roll API types, copy-paste Dockerfiles from the last project, debug Helm values, and pray the deployment pipeline doesn't choke. By the time the service is live, you've forgotten what you were actually trying to build.

We built Riven to kill that entire loop. Our proto-first workflow means you define your API in a .proto file, and the platform handles everything else: scaffolding, codegen, type-safe clients, Docker images, and Kubernetes deployment. In this tutorial, we'll walk through the entire flow — from zero to a running service — in about ten minutes.

Prerequisites: One Command to Check Them All

Before we start, make sure the Riven CLI is installed globally. Then authenticate and verify your environment:

riven auth login

This opens your browser, walks you through authentication, and stores your token at ~/.config/riven/credentials.json. Once you're logged in, run the doctor:

riven doctor

The doctor checks for Node.js 22+, Yarn 4, Buf, Docker, kubectl, Helm, and connectivity to the proto registry. Green across the board means we're ready to go.

Step 1: Scaffold the Service

One command creates everything we need:

riven service create order-service --lang node

This generates 12+ files in a single shot. Build and config: package.json, tsconfig.json, buf.yaml — all pre-configured with the right dependencies, module resolution (NodeNext), and Buf plugin references. Infrastructure: A two-line Dockerfile (yes, two lines — our base image handles everything via ONBUILD) and a helm-values.yaml with sane defaults including port 8080. Application code: src/server.ts with Express and ConnectRPC wired up, src/service.ts with the ServiceBase implementation ready to fill in, and src/config.ts with Zod-based environment validation. The proto contract: proto/com/riven/platform/order_service/v1/service.proto — a starter file with CRUD operations, HTTP annotations, and buf.validate rules.

No copying from other repos. No "let me check how the last service did it." Everything is consistent from the start.

Step 2: Define Your API

This is the only creative step. Open the generated .proto file and define the contract for your service. Proto is the source of truth — not TypeScript interfaces, not OpenAPI specs, not a wiki page that's three months out of date.

We define our messages, add field-level validation with buf.validate, annotate RPCs with HTTP bindings, and we're done. The schema is clean, versioned, and machine-readable. If another team needs to consume this service, they'll pull this proto — not guess at a JSON shape.

Step 3: Generate Everything

Now we let the machines do the boring part:

riven proto generate

This runs three Buf plugins — protoc-gen-es, our custom protoc-gen-riven, and protoc-gen-connect-query — and produces the src/__generated__/ directory containing:

Plain TypeScript types (*_riven_pb.ts) — fully typed request and response objects. No any, no casting, no guessing. Abstract service base (*ServiceBase.ts) — a class with every RPC method as an abstract method. Your IDE tells you exactly what to implement. Typed client (*Client.ts) — import it in any other service to call this one with full type safety. Method metadata (*Meta.ts) — used by our platform-auth interceptor for authorization checks. TanStack Query hooks (*_connectquery.ts) — drop these into a React frontend and you have data fetching with caching, optimistic updates, and type safety out of the box. Binary descriptor (descriptor.binpb) — used for gRPC reflection and runtime schema access.

All generated code is excluded from linting and tests. You never edit it. You never commit manual changes to it. It's derived from the proto, always.

Step 4: Implement the Logic

Open src/service.ts. The generated ServiceBase class has abstract methods for every RPC in your proto. Your IDE lights up with exactly what needs implementing — method signatures, input types, return types, all inferred. Write the business logic, hit save. No wiring, no route registration, no serialization code.

Build the project to make sure everything compiles cleanly:

yarn build

This runs riven proto generate under the hood (so generated code is always fresh) followed by tsc. If it compiles, your implementation matches your contract. The type system is your first line of defense.

Step 5: Ship It

Remember that two-line Dockerfile? Our node-service-base image uses ONBUILD triggers to handle dependency resolution, tree-shaking, and assembly. You don't configure any of it. Just publish:

riven publish --tag v1.0.0

This builds the Docker image and pushes it to ECR. Then deploy:

riven dev-center service deploy order-service --tag v1.0.0

The CLI talks to our dev-center backend, which orchestrates the Kubernetes deployment through Pulumi — no raw kubectl, no Helm commands, no state drift. Monitor the rollout:

riven dev-center rollout status order-service

Green. The service is live, listening on port 8080, discoverable by every other service on the platform.

Sharing Your API

Your proto isn't just for your service. Publish it to the registry so other teams can consume it:

riven proto publish

Other services pull your proto with riven proto pull, search the registry with riven proto search, and generate typed clients on their end. Need to check for breaking changes before merging? Run riven proto breaking in CI. The contract is enforced at every stage.

For service-to-service communication, add a riven.rpcClients entry in your package.json, and the codegen produces a ready-to-use typed client. For frontend consumption, the generated TanStack Query hooks mean your React components get the same type safety as the backend.

What Just Happened

In roughly ten minutes, we went from nothing to a production service with: a validated proto contract, generated types and clients, a two-line Dockerfile, a Kubernetes deployment managed through our platform, and a published API that any team can discover and consume with full type safety.

No YAML archaeology. No boilerplate cargo-culting. No "it works on my machine." Just define the contract, implement the logic, and ship. That's what proto-first development looks like when the platform does its job.