Learnixo
Back to blog
Backend Systemsintermediate

gRPC vs REST โ€” When to Choose Each

A practical decision guide: when to use gRPC vs REST in .NET systems. Performance comparison, streaming, browser support, contract-first design, and real-world architecture patterns.

Asma Hafeez KhanMay 24, 20266 min read
.NETC#gRPCRESTAPI designmicroservicesarchitecture
Share:๐•

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."

gRPC Knowledge Check

5 questions ยท Test what you just learned ยท Instant explanations

Enjoyed this article?

Explore the Backend Systems learning path for more.

Found this helpful?

Share:๐•

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.