gRPC and REST are the two dominant choices for building backend APIs and internal microservice communication. REST has been the default since 2000, powering virtually every public API. gRPC, open-sourced by Google in 2016, promises higher performance, strong typing, and native streaming — at the cost of complexity and browser support limitations. In 2025, the choice genuinely affects latency, developer experience, and system scalability.
This guide gives you a practical, honest comparison so you can choose the right tool for your situation.
At a glance
| REST | gRPC | |
|---|---|---|
| Created by | Roy Fielding, 2000 | Google, 2016 |
| Protocol | HTTP/1.1 or HTTP/2 | HTTP/2 (required) |
| Data format | JSON (text) | Protocol Buffers (binary) |
| Schema | Optional (OpenAPI/Swagger) | Required (.proto file) |
| Code generation | Optional | Built-in (all major languages) |
| Type safety | Optional (TypeScript, Zod) | Built-in from .proto |
| Streaming | Limited (SSE, long-polling) | Native (4 modes) |
| Browser support | Native | Requires gRPC-Web proxy |
| Learning curve | Low | Moderate |
| Best for | Public APIs, CRUD, browser clients | Microservices, real-time, polyglot systems |
What is REST?
REST (Representational State Transfer) is an architectural style that uses standard HTTP methods (GET, POST, PUT, DELETE) on resource-based URLs. Responses are typically JSON.
GET /users/42 → returns user object (JSON)
POST /users → creates a new user
PUT /users/42 → updates user
DELETE /users/42 → deletes user
REST's strengths: every browser, HTTP client, and language supports it natively. Responses are human-readable JSON. CDNs cache GET requests automatically. Postman, curl, and browser DevTools work out of the box.
What is gRPC?
gRPC is a high-performance RPC (Remote Procedure Call) framework. You define your API in a .proto file using Protocol Buffers (Protobuf), then generate client and server code in any supported language.
// user.proto
syntax = "proto3";
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc CreateUser (CreateUserRequest) returns (User);
rpc StreamUsers (StreamUsersRequest) returns (stream User);
}
message User {
int32 id = 1;
string name = 2;
string email = 3;
}
message GetUserRequest {
int32 id = 1;
}
From this single .proto, protoc generates type-safe client and server stubs in Go, Python, Java, Node.js, C#, Rust, and more. You call remote services like local function calls.
Core differences
Transport and serialization
| Dimension | REST | gRPC |
|---|---|---|
| HTTP version | HTTP/1.1 (common) or HTTP/2 | HTTP/2 only |
| Serialization | JSON (text, human-readable) | Protobuf (binary, compact) |
| Payload size | Larger (verbose key names) | 3–10× smaller than JSON |
| Parse speed | Slower (string parsing) | Faster (binary decode) |
| Human readable | Yes | No (needs protoc decode) |
Type system and schema
REST leaves typing optional. You can document with OpenAPI/Swagger, add TypeScript types manually, or validate with Zod — but nothing enforces correctness across service boundaries.
gRPC's .proto file is the contract. Mismatched field types or missing required fields cause a compile-time error in generated code, not a runtime 500 at 2 AM.
Code generation
# gRPC: generate client + server for Go from .proto
protoc --go_out=. --go-grpc_out=. user.proto
# REST: generate client from OpenAPI (optional, ecosystem-dependent)
openapi-generator generate -i openapi.yaml -g typescript-fetch -o ./client
gRPC code generation is a first-class feature. REST code generation exists but is an afterthought.
REST vs gRPC: code side by side
REST endpoint (Node.js / Express)
// Server
app.get('/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
if (!user) return res.status(404).json({ error: 'Not found' });
res.json({ id: user.id, name: user.name, email: user.email });
});
// Client (browser or Node)
const response = await fetch('/users/42');
const user = await response.json();
gRPC service (Node.js)
// Server (Node.js grpc library)
const server = new grpc.Server();
server.addService(UserService, {
getUser: async (call, callback) => {
const user = await db.users.findById(call.request.id);
if (!user) return callback({ code: grpc.status.NOT_FOUND });
callback(null, { id: user.id, name: user.name, email: user.email });
},
});
// Client
const client = new UserServiceClient('localhost:50051', grpc.credentials.createInsecure());
client.getUser({ id: 42 }, (err, user) => {
console.log(user.name);
});
gRPC is more boilerplate to set up but gives you a fully typed client with zero hand-written code.
Performance
| Scenario | REST (JSON/HTTP1.1) | gRPC (Protobuf/HTTP2) | Winner |
|---|---|---|---|
| Single request latency | ~2–5ms | ~0.5–1ms | gRPC |
| Payload size (1,000 users) | ~180 KB | ~20–50 KB | gRPC |
| Throughput (reqs/sec) | ~10,000 | ~50,000+ | gRPC |
| Connection overhead | New TCP per request (HTTP/1.1) | Multiplexed streams (HTTP/2) | gRPC |
| Cold start (first request) | Fast | Slightly slower (proto handshake) | REST |
| CDN cacheable | Yes (GET requests) | No | REST |
| Browser native | Yes | No (gRPC-Web proxy needed) | REST |
| Debugging ease | curl, Postman, browser | grpcurl, BloomRPC, Evans | REST |
Rule of thumb: gRPC is typically 2–10× faster for internal service-to-service calls. REST wins on simplicity and browser compatibility.
Streaming — gRPC's superpower
gRPC natively supports four communication patterns:
| Pattern | Description | Use case |
|---|---|---|
| Unary | Single request → single response | Standard CRUD (like REST) |
| Server streaming | Single request → stream of responses | Live feeds, large data exports |
| Client streaming | Stream of requests → single response | File uploads, telemetry batch |
| Bidirectional streaming | Stream in both directions simultaneously | Chat, collaborative editing, games |
service ChatService {
rpc SendMessage (Message) returns (Message); // Unary
rpc Subscribe (SubscribeRequest) returns (stream Message); // Server stream
rpc UploadLog (stream LogEntry) returns (UploadResult); // Client stream
rpc Chat (stream Message) returns (stream Message); // Bidirectional
}
REST can approximate server streaming via SSE (Server-Sent Events) for one-way feeds, and WebSockets for bidirectional — but these are separate technologies bolted on, not first-class features.
Browser support gap
This is gRPC's biggest limitation. Browsers cannot make raw HTTP/2 gRPC calls because they don't expose low-level framing control.
Workaround: gRPC-Web — a modified protocol with a browser-compatible encoding, plus a proxy (Envoy or grpc-gateway) that translates between gRPC-Web and native gRPC.
Browser → gRPC-Web request → Envoy proxy → gRPC backend
This adds operational complexity. For browser-facing APIs, REST or GraphQL is almost always simpler. gRPC shines for backend-to-backend communication.
Error handling
| REST | gRPC | |
|---|---|---|
| Error model | HTTP status codes (200, 400, 404, 500) | gRPC status codes (14 codes: OK, NOT_FOUND, UNAVAILABLE, etc.) |
| Error details | JSON body (custom structure) | google.rpc.Status with metadata |
| Standard codes | Well-known, understood by CDNs/proxies | Less familiar, own tooling needed |
| Rich error details | Manual (put in JSON body) | ErrorDetails proto (field violations, retry info) |
gRPC status codes like UNAVAILABLE, DEADLINE_EXCEEDED, RESOURCE_EXHAUSTED map naturally to distributed systems concepts — often more precise than HTTP 500 for internal errors.
Tooling ecosystem
| Category | REST | gRPC |
|---|---|---|
| API testing | Postman, Insomnia, curl | grpcurl, Evans, BloomRPC, Postman (v10+) |
| Documentation | Swagger UI, Redoc (OpenAPI) | Generated from .proto + buf.build docs |
| Gateways / proxies | Kong, nginx, AWS API Gateway | Envoy, grpc-gateway, gRPC-Web proxy |
| Client generation | openapi-generator (mixed quality) | protoc (official, high quality) |
| Schema registry | No standard (OpenAPI files in git) | Buf Schema Registry (BSR) |
| Load balancers | All HTTP LBs | HTTP/2-aware LB required (ALB, Envoy) |
| Monitoring | Standard HTTP metrics (status codes) | gRPC-specific interceptors + Prometheus |
| Language support | Every language | 11 officially supported languages |
Interoperability: polyglot microservices
This is where gRPC shines brightest. In a system where services are written in Go, Python, Java, and C#, sharing a .proto file gives every team a type-safe client in their own language — generated automatically.
With REST, teams either:
- Hand-write clients (error-prone, drift from server)
- Share OpenAPI specs and generate clients (works but more setup, quality varies)
user.proto → Go client (auth-service)
→ Python client (ml-service)
→ Java client (billing-service)
→ Node.js client (api-gateway)
One schema, four typed clients, zero manual work.
Where REST wins
| Scenario | Why REST |
|---|---|
| Public APIs | Consumers use any language/tool; JSON is universal |
| Browser clients | Native fetch support; no gRPC-Web proxy |
| CRUD web apps | Simple resource model maps naturally to HTTP verbs |
| CDN-cached responses | HTTP GET caching works out of the box |
| Small teams | Lower setup overhead; curl debugging |
| Third-party integrations | Webhooks, payment APIs, OAuth all use REST |
| Mobile apps (simple) | REST + JSON is well-supported everywhere |
| Serverless functions | REST integrates with API Gateway natively |
Where gRPC wins
| Scenario | Why gRPC |
|---|---|
| Internal microservices | 5–10× lower latency, HTTP/2 multiplexing |
| Polyglot systems | One .proto → typed clients in all languages |
| Real-time streaming | Native bidirectional streaming |
| High-throughput pipelines | Protobuf binary is smaller and faster to parse |
| Mobile backend (bandwidth-critical) | 60–80% smaller payloads = less data usage |
| Cross-team API contracts | .proto as a strict, versioned contract |
| ML model serving | TensorFlow Serving, Triton use gRPC natively |
| IoT/embedded systems | Binary protocol, low overhead |
Decision guide
Who are your API consumers?
├── Browser / public internet → REST (or GraphQL)
└── Internal services only → gRPC (especially if performance matters)
Do you need streaming?
├── Yes (bidirectional or server push) → gRPC
└── No → either works; REST is simpler
Is payload size / latency critical?
├── Yes (high-throughput, mobile) → gRPC
└── No → REST's simplicity wins
Polyglot team?
├── Yes (Go + Python + Java) → gRPC (one .proto, all clients)
└── No (one language) → REST is fine
Team experience?
├── Familiar with Protobuf → gRPC
└── New team or external consumers → REST
Full comparison
| Feature | REST | gRPC |
|---|---|---|
| Protocol | HTTP/1.1 + HTTP/2 | HTTP/2 only |
| Data format | JSON (text) | Protocol Buffers (binary) |
| Schema | Optional (OpenAPI) | Required (.proto) |
| Code generation | Optional, community tooling | First-class, official tooling |
| Type safety | Optional | Built-in |
| Streaming | SSE / WebSocket (bolt-on) | Native (4 modes) |
| Browser support | Native | gRPC-Web proxy required |
| Performance | Good | Excellent (2–10× faster) |
| Payload size | Larger (verbose JSON) | 3–10× smaller |
| Caching | HTTP-native (CDN, browser) | No native caching |
| Error model | HTTP status codes | gRPC status codes |
| Learning curve | Low | Moderate |
| Debugging | curl, browser DevTools | grpcurl, Evans |
| Ecosystem maturity | Very mature (25+ years) | Mature (8+ years) |
| Public API suitability | Excellent | Poor (browser limitation) |
| Internal API suitability | Good | Excellent |
| Contract versioning | Manual (URL versioning) | Proto field numbers + deprecation |
| Community size | Massive | Large (growing) |
gRPC vs REST vs GraphQL vs SOAP
| REST | gRPC | GraphQL | SOAP | |
|---|---|---|---|---|
| Protocol | HTTP | HTTP/2 | HTTP | HTTP/SMTP/TCP |
| Format | JSON | Protobuf | JSON | XML |
| Schema | Optional | Required | Required (SDL) | Required (WSDL) |
| Typing | Optional | Strong | Strong | Strong |
| Streaming | SSE/WS | Native | Subscriptions | No |
| Browser | Native | Proxy needed | Native | Native |
| Performance | Good | Excellent | Good | Poor |
| Best for | Public APIs | Microservices | Data graphs | Enterprise legacy |
| Learning curve | Low | Moderate | Moderate | High |
Common mistakes
| Mistake | Problem | Fix |
|---|---|---|
| Using gRPC for public-facing APIs | Browser clients can't call gRPC natively | Add gRPC-Web proxy or expose REST gateway |
Skipping .proto versioning |
Removing or changing field numbers breaks clients | Never remove fields; use reserved keyword |
| Not pinning proto dependencies | Subtle breaking changes across service deploys | Use Buf with buf.lock or commit generated code |
| Forgetting deadlines/timeouts | gRPC calls block indefinitely without timeout | Always set WithTimeout or DialOption |
| Using gRPC for simple CRUD web app | Unnecessary complexity; REST is fine | Only adopt gRPC when performance or streaming is needed |
| Ignoring HTTP/2 load balancer requirements | Round-robin LB sends all traffic to one pod | Use Envoy, ALB (AWS), or headless Kubernetes service |
| Not generating code from proto | Hand-written clients diverge from server contract | Always use protoc or buf generate |
| Building gRPC without TLS in production | HTTP/2 requires TLS for most browsers and proxies | Always use TLS in production (gRPC uses credentials.NewTLS) |
Frequently asked questions
Is gRPC faster than REST? Yes, typically 2–10× for internal service calls. gRPC uses HTTP/2 multiplexing (no head-of-line blocking), Protobuf binary (3–10× smaller than JSON), and persistent connections. The difference matters most in high-throughput pipelines and latency-sensitive paths.
Can I use gRPC in a browser? Not natively. Browsers cannot make raw HTTP/2 gRPC calls. You need gRPC-Web — a modified protocol plus an Envoy or grpc-gateway proxy. For browser-facing APIs, REST or GraphQL is almost always simpler.
Should I use gRPC or REST for microservices?
gRPC is generally the better choice for internal microservice communication: lower latency, smaller payloads, native streaming, and polyglot code generation from a single .proto. Use REST (or gRPC-gateway) only if external clients or legacy services need HTTP/JSON.
Can I use both gRPC and REST?
Yes — this is common. Use REST for public APIs and browser clients; use gRPC internally between services. Tools like grpc-gateway can expose a REST JSON API and a gRPC API from the same server automatically.
What is Protocol Buffers (Protobuf)?
Protobuf is Google's binary serialization format. It's ~3–10× smaller than equivalent JSON and significantly faster to serialize/deserialize. The trade-off: binary data isn't human-readable without protoc tooling. Field numbers in .proto files ensure backward compatibility.
Is gRPC good for mobile apps?
Yes — the compact Protobuf payloads reduce bandwidth usage significantly (important on mobile networks). Google uses gRPC internally for many Android/iOS backend APIs. However, you still need client libraries (grpc-swift, grpc-java, etc.) rather than plain fetch.