.NET & C# Development · Lesson 147 of 229
gRPC vs REST — When to Choose Each
gRPC vs REST — When to Choose Each
Both gRPC and REST are HTTP-based API styles, but they make very different trade-offs. The wrong choice doubles latency or blocks browser clients. This guide gives you a clear decision framework.
Side-by-Side Comparison
Feature REST gRPC
─────────────────────────────────────────────────────────────────
Protocol HTTP/1.1 or HTTP/2 HTTP/2 (required)
Serialisation JSON (text) Protobuf (binary)
Contract OpenAPI (optional) .proto (required)
Type safety Weak (runtime) Strong (compile-time)
Payload size Larger 5–10× smaller
Browser support Native Needs gRPC-Web proxy
Streaming SSE / WebSocket (add-on) Native (4 types)
Human-readable Yes No (binary)
Code generation Optional Mandatory (client stubs)
Error model HTTP status codes StatusCode enum + detail
Ecosystem tooling Curl, Postman, Swagger grpcurl, Kreya, PostmanWhen to Use REST
✓ Public-facing APIs — browsers and third-party developers use your API
✓ Mobile clients — REST over HTTP/1.1 works everywhere
✓ Simple CRUD — GET/POST/PUT/DELETE maps naturally
✓ Team unfamiliarity with Protobuf — lower learning curve
✓ Caching requirements — HTTP GET is natively cacheable
✓ Webhooks and callbacks — HTTP POST from external systems
✓ You need human-readable traffic for debugging
Examples in .NET:
- Customer-facing public API (Stripe, GitHub-style)
- Mobile app backend
- Partner integrations
- Admin dashboards// REST — natural for public CRUD APIs
[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
[HttpGet("{id}")]
[ProducesResponseType<OrderDto>(200)]
[ProducesResponseType(404)]
public async Task<IActionResult> GetOrder(int id, CancellationToken ct)
{
var order = await _repo.GetByIdAsync(id, ct);
return order is null ? NotFound() : Ok(order);
}
}When to Use gRPC
✓ Service-to-service communication (internal microservices)
✓ High-throughput, low-latency paths (payment processing, order routing)
✓ Streaming large datasets (feed ingestion, real-time updates)
✓ Polyglot systems — auto-generate clients in Go, Python, Java, C#
✓ Strong contract enforcement — .proto is the API
✓ Bidirectional streaming — chat, live dashboards, IoT
✓ Network-constrained environments — mobile backend-to-backend, embedded
Examples in .NET:
- OrderService ↔ InventoryService (internal)
- Payment gateway consumer
- Real-time order tracking stream
- IoT device data ingestion// gRPC — natural for internal service calls
public class InventoryCheckHandler(InventoryService.InventoryServiceClient grpc)
: IRequestHandler<CheckInventoryQuery, InventoryResult>
{
public async Task<InventoryResult> Handle(CheckInventoryQuery q, CancellationToken ct)
{
var response = await grpc.CheckStockAsync(
new CheckStockRequest { ProductId = q.ProductId, Quantity = q.Quantity },
deadline: DateTime.UtcNow.AddSeconds(2),
cancellationToken: ct);
return new InventoryResult(response.Available, response.ReservedUntil.ToDateTime());
}
}Performance Comparison
// Benchmarking REST vs gRPC for the same payload
// Order with 10 items — approximate results:
// REST + JSON:
// Serialised size: ~850 bytes
// Parse time: ~12 µs
// Throughput: ~50,000 req/s per core
// gRPC + Protobuf:
// Serialised size: ~120 bytes (7× smaller)
// Parse time: ~2 µs (6× faster)
// Throughput: ~200,000 req/s per core
// The gap widens with:
// - Larger payloads (lists, nested objects)
// - High call frequency (tight service loops)
// - Limited bandwidth (mobile, edge)
// The gap narrows with:
// - Small single-field responses (IDs, booleans)
// - Low call frequency (< 100 req/s)gRPC-Web: gRPC in the Browser
Standard gRPC uses HTTP/2 trailers — browsers cannot access these.
gRPC-Web is a subset that works in browsers via a proxy (Envoy or ASP.NET Core).
dotnet add package Grpc.AspNetCore.Web
app.UseGrpcWeb();
app.MapGrpcService().EnableGrpcWeb();
// Client (TypeScript):
import { OrderServiceClient } from './generated/orders_grpc_web_pb';
const client = new OrderServiceClient('https://api.example.com'); Hybrid Architecture (Real-World Pattern)
Most production .NET systems use both:
[Browser / Mobile]
│
│ REST + JSON (public API)
▼
[API Gateway / BFF]
│
│ gRPC (internal, high-performance)
├──────────────────────────────┐
▼ ▼
[OrderService] [InventoryService]
│ │
│ gRPC │ gRPC
▼ ▼
[PaymentService] [WarehouseService]// BFF (Backend for Frontend) — REST in, gRPC out
[ApiController]
[Route("api/checkout")]
public class CheckoutController(
OrderService.OrderServiceClient orderGrpc,
InventoryService.InventoryServiceClient inventoryGrpc) : ControllerBase
{
[HttpPost]
public async Task<IActionResult> Checkout(CheckoutRequest request, CancellationToken ct)
{
// Internal: fast gRPC calls
var stock = await inventoryGrpc.CheckStockAsync(
new CheckStockRequest { ProductId = request.ProductId, Quantity = request.Qty },
cancellationToken: ct);
if (!stock.Available)
return Conflict("Item out of stock");
var order = await orderGrpc.CreateOrderAsync(
new CreateOrderRequest { CustomerId = request.CustomerId },
cancellationToken: ct);
// External: REST response to browser
return Ok(new { OrderId = order.Id, Total = order.Total });
}
}Contract Evolution
// Protobuf is forward and backward compatible if you follow the rules:
message OrderResponse {
int32 id = 1; // field numbers never change
double total = 2;
string status = 3;
// ADD new optional fields with new field numbers:
string tracking_code = 4; // old clients ignore unknown fields — safe
// NEVER reuse a field number — causes silent data corruption
// NEVER rename a field in a way that changes the number — safe to rename the name
// NEVER change a field type without careful thought
}// REST versioning requires URL versioning or headers:
// GET /api/v2/orders/42
// gRPC versioning via package name in .proto:
// package orders.v2;
// Or: run old and new service side-by-side, route at the gatewayDecision Checklist
Q: Will browsers call this API directly?
→ Yes: REST (or gRPC-Web with proxy)
→ No: gRPC is viable
Q: Is this an internal service-to-service call?
→ Yes: gRPC — faster, typed, streaming built-in
→ No: Depends on caller
Q: Do you need streaming?
→ Yes: gRPC — native bidirectional streaming
→ No: Either works
Q: Is the team unfamiliar with Protobuf?
→ Yes: REST — lower friction initially
→ No: gRPC
Q: Do third parties integrate with this API?
→ Yes: REST — universal client support
→ No: gRPC if internal
Q: Do you need HTTP caching (CDN, ETags)?
→ Yes: REST — GET is natively cacheable
→ No: Either worksInterview Answer
"REST and gRPC solve different problems. REST uses JSON over HTTP with a flexible, discoverable interface — best for public APIs, browser clients, and third-party integrations where human-readability and caching matter. gRPC uses Protobuf over HTTP/2 — best for internal service-to-service communication where performance, streaming, and strong typing matter. Key differences: gRPC payloads are 5–10x smaller (binary vs JSON), HTTP/2 enables multiplexing and native streaming, and the
.protofile enforces a compile-time contract. In practice, production .NET microservice architectures use both: REST at the public edge (BFF, API Gateway) and gRPC for internal service calls. The main constraint: gRPC doesn't work natively in browsers — use gRPC-Web with Envoy or ASP.NET Core'sUseGrpcWeb()middleware."