Learnixo

.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, Postman

When 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
C#
// 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
C#
// 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

C#
// 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]
C#
// 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
// 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
}
C#
// 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 gateway

Decision 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 works

Interview 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 .proto file 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's UseGrpcWeb() middleware."