Waqas Ahmad — Software Architect & Technical Consultant - Available USA, Europe, Global

Waqas Ahmad — Software Architect & Technical Consultant

Specializing in

Distributed Systems

.NET ArchitectureCloud-Native ArchitectureAzure Cloud EngineeringAPI ArchitectureMicroservices ArchitectureEvent-Driven ArchitectureDatabase Design & Optimization

👋 Hi, I'm Waqas — a Software Architect and Technical Consultant specializing in .NET, Azure, microservices, and API-first system design..
I help companies build reliable, maintainable, and high-performance backend platforms that scale.

Experienced across engineering ecosystems shaped by Microsoft, the Cloud Native Computing Foundation, and the Apache Software Foundation.

Available for remote consulting (USA, Europe, Global) — flexible across EST, PST, GMT & CET.

services
Article

gRPC vs REST for .NET APIs: When to Choose Which

gRPC vs REST in .NET: performance, contract-first design, and when to use each.

services
Read the article

Introduction

This guidance is relevant when the topic of this article applies to your system or design choices; it breaks down when constraints or context differ. I’ve applied it in real projects and refined the takeaways over time (as of 2026).

Choosing the wrong API style for your clients and performance needs leads to compatibility gaps or unnecessary complexity. This article compares gRPC and REST in .NET: what each is, strengths and weaknesses, implementation in ASP.NET Core, streaming, and when to choose which. For architects and tech leads, picking REST for public and browser clients and gRPC for internal, high-throughput services (or both in one architecture) keeps systems fit for purpose.

For a deeper overview of this topic, explore the full .NET Architecture guide.

Decision Context

  • System scale: APIs serving internal services, external clients, or both; from a few endpoints to many. Applies when you’re choosing the API style and transport for .NET services.
  • Team size: Backend and sometimes front-end teams; someone must own .proto contracts (gRPC) or OpenAPI/contracts (REST). Works when clients can use HTTP/JSON (REST) or HTTP/2 + protobuf (gRPC).
  • Time / budget pressure: Fits greenfield and “we need performance” or “we need browser compatibility”; breaks down when the team has no gRPC experience and must ship quickly—then REST is safer.
  • Technical constraints: .NET (ASP.NET Core); REST with JSON or gRPC with Protocol Buffers; HTTP/1.1 or HTTP/2. Browser clients typically need REST (or gRPC-Web); server-to-server can use gRPC.
  • Non-goals: This article does not optimise for GraphQL or other styles; it focuses on REST vs gRPC in .NET and when to choose which.

What is REST?

REST (Representational State Transfer) is an architectural style for APIs. You expose resources as URLs and use HTTP methods to operate on them:

Method Purpose Example
GET Read GET /api/orders/123
POST Create POST /api/orders
PUT Replace PUT /api/orders/123
PATCH Update PATCH /api/orders/123
DELETE Delete DELETE /api/orders/123

Key characteristics:

  • Stateless: Each request contains all information needed
  • Cacheable: GET responses can be cached (CDN, browser, proxy)
  • Human-readable: JSON payloads are easy to inspect
  • Tool-friendly: curl, Postman, Swagger all work out of the box
  • Ubiquitous: Every language and platform supports HTTP/JSON

What is gRPC?

gRPC (gRPC Remote Procedure Calls) is a high-performance RPC framework developed by Google. It uses:

  • HTTP/2 for transport (multiplexing, header compression)
  • Protocol Buffers for serialisation (binary, strongly typed)
  • Contract-first design via .proto files

Key characteristics:

  • Fast: Binary serialisation is 5-10x smaller than JSON; HTTP/2 multiplexing reduces latency
  • Streaming: Supports client, server, and bidirectional streaming
  • Strongly typed: .proto files define contracts; code is generated
  • Cross-platform: Clients and servers in any language (C#, Go, Java, Python, etc.)

What are Protocol Buffers?

Protocol Buffers (protobuf) are a binary serialisation format with a schema definition language. You define messages in a .proto file:

// orders.proto
syntax = "proto3";

package orders;

service OrderService {
  rpc CreateOrder (CreateOrderRequest) returns (CreateOrderResponse);
  rpc GetOrder (GetOrderRequest) returns (Order);
  rpc ListOrders (ListOrdersRequest) returns (stream Order); // server streaming
}

message CreateOrderRequest {
  string customer_id = 1;
  repeated OrderItem items = 2;
}

message CreateOrderResponse {
  string order_id = 1;
}

message GetOrderRequest {
  string order_id = 1;
}

message Order {
  string id = 1;
  string customer_id = 2;
  string status = 3;
  repeated OrderItem items = 4;
}

message OrderItem {
  string product_id = 1;
  int32 quantity = 2;
  double price = 3;
}

message ListOrdersRequest {
  string customer_id = 1;
}

From this .proto file, the tooling generates C# classes and service stubs. The binary format is compact and fast to serialise/deserialise.

gRPC vs REST: comparison table

Aspect REST gRPC
Transport HTTP/1.1 or HTTP/2 HTTP/2 only
Serialisation JSON (text) Protocol Buffers (binary)
Contract OpenAPI/Swagger (optional) .proto (required)
Streaming Limited (SSE, WebSocket) Built-in (client, server, bidirectional)
Browser support Native gRPC-Web (proxy required)
Caching HTTP caching works No HTTP caching
Debugging Easy (JSON is readable) Needs tooling (binary)
Code generation Optional Required
Performance Good Excellent
Adoption Universal Growing

REST implementation in ASP.NET Core

A typical REST API in ASP.NET Core:

// OrdersController.cs
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    private readonly IOrderService _orderService;

    public OrdersController(IOrderService orderService)
        => _orderService = orderService;

    [HttpGet("{id}")]
    public async Task<ActionResult<OrderDto>> GetOrder(string id)
    {
        var order = await _orderService.GetByIdAsync(id);
        return order == null ? NotFound() : Ok(order);
    }

    [HttpPost]
    public async Task<ActionResult<OrderDto>> CreateOrder(CreateOrderRequest request)
    {
        var order = await _orderService.CreateAsync(request);
        return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
    }

    [HttpGet]
    public async Task<ActionResult<IEnumerable<OrderDto>>> ListOrders([FromQuery] string customerId)
        => Ok(await _orderService.ListByCustomerAsync(customerId));
}
// Program.cs
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.MapControllers();
app.Run();

gRPC implementation in ASP.NET Core

A typical gRPC service in ASP.NET Core:

1. Add the NuGet package:

dotnet add package Grpc.AspNetCore

2. Add the .proto file to your project:

<!-- In .csproj -->
<ItemGroup>
  <Protobuf Include="Protosorders.proto" GrpcServices="Server" />
</ItemGroup>

3. Implement the service:

// OrderServiceImpl.cs
public class OrderServiceImpl : OrderService.OrderServiceBase
{
    private readonly IOrderRepository _repository;
    private readonly ILogger<OrderServiceImpl> _logger;

    public OrderServiceImpl(IOrderRepository repository, ILogger<OrderServiceImpl> logger)
    {
        _repository = repository;
        _logger = logger;
    }

    public override async Task<CreateOrderResponse> CreateOrder(
        CreateOrderRequest request, ServerCallContext context)
    {
        _logger.LogInformation("Creating order for customer {CustomerId}", request.CustomerId);
        
        var orderId = await _repository.CreateAsync(request);
        return new CreateOrderResponse { OrderId = orderId };
    }

    public override async Task<Order> GetOrder(
        GetOrderRequest request, ServerCallContext context)
    {
        var order = await _repository.GetByIdAsync(request.OrderId);
        if (order == null)
        {
            throw new RpcException(new Status(StatusCode.NotFound, "Order not found"));
        }
        return order;
    }
}

4. Register in Program.cs:

// Program.cs
builder.Services.AddGrpc();

var app = builder.Build();
app.MapGrpcService<OrderServiceImpl>();
app.Run();

Streaming with gRPC

gRPC supports three streaming modes:

Mode Description Use case
Server streaming Server sends multiple messages List large datasets, real-time updates
Client streaming Client sends multiple messages Upload batches, aggregations
Bidirectional Both send multiple messages Chat, real-time collaboration

Server streaming example:

public override async Task ListOrders(
    ListOrdersRequest request,
    IServerStreamWriter<Order> responseStream,
    ServerCallContext context)
{
    await foreach (var order in _repository.GetOrdersAsync(request.CustomerId))
    {
        if (context.CancellationToken.IsCancellationRequested)
            break;
            
        await responseStream.WriteAsync(order);
    }
}

Client code for server streaming:

using var call = client.ListOrders(new ListOrdersRequest { CustomerId = "cust-123" });

await foreach (var order in call.ResponseStream.ReadAllAsync())
{
    Console.WriteLine($"Order: {order.Id} - {order.Status}");
}

When to choose REST

Choose REST when:

  • Clients are browsers or third-party consumers
  • You need HTTP caching (CDN, browser cache)
  • Maximum interoperability is required
  • Payloads are small and latency is not critical
  • You want easy debugging and inspection
  • Team is more familiar with REST

REST is the default for public APIs and web applications.

When to choose gRPC

Choose gRPC when:

  • Clients are your own services (microservices communication)
  • You need low latency and high throughput
  • You want streaming (real-time data, large result sets)
  • You prefer contract-first with strong typing
  • You control both client and server
  • Performance is critical

gRPC excels in service-to-service communication within a microservices architecture.

Enterprise best practices

1. Use REST at the edge, gRPC internally. Expose REST APIs for external consumers and browsers; use gRPC for internal service-to-service communication. An API Gateway can translate.

2. Version your contracts. REST: URL versioning (/api/v1/orders) or header versioning. gRPC: evolve .proto files backward-compatibly (add fields, do not remove).

3. Handle errors consistently. REST: use HTTP status codes and problem details (RFC 7807). gRPC: use StatusCode and RpcException.

4. Add deadlines and timeouts. gRPC supports deadlines natively (context.Deadline). For REST, use HttpClient timeouts and cancellation tokens.

5. Implement retries and circuit breakers. Use Polly with HttpClient for REST. gRPC supports retry policies via GrpcChannelOptions.

6. Secure with TLS. Both REST and gRPC should use TLS in production. gRPC requires HTTP/2, which typically means TLS.

7. Add observability. Trace requests with OpenTelemetry. Log with structured logging. gRPC integrates with Grpc.AspNetCore instrumentation.

8. Use gRPC-Web for browsers. If you must call gRPC from browsers, use gRPC-Web with an Envoy proxy or ASP.NET Core gRPC-Web middleware.

Common issues

Issue REST gRPC
Browser support Native Needs gRPC-Web proxy
Firewall/proxy Works everywhere Some proxies block HTTP/2
Debugging Easy (JSON) Needs tooling (grpcurl, Bloom)
Contract changes Can be implicit Must update .proto
Error handling HTTP status codes StatusCode enum
Caching HTTP caching works No HTTP caching
Learning curve Low Medium

You can also explore more patterns in the .NET Architecture resource page.

Summary

REST and gRPC serve different purposes: REST for public APIs, browser clients, and maximum compatibility; gRPC for internal services, streaming, and high throughput when you control both ends. Using gRPC where browsers or third parties must integrate causes friction; using REST everywhere can limit performance between services—many architectures use both (REST at the edge, gRPC internally). Next, list your clients and performance needs, then choose REST for the edge and gRPC for service-to-service where it pays off; implement both in ASP.NET Core and document which endpoints are which.

Position & Rationale

I use REST when the API is public, consumed by browsers or third parties, or when I need HTTP caching and broad tooling support—it’s the default for “API that anyone can call.” I use gRPC when the callers are internal services, when I need streaming (client, server, or bidirectional), or when performance and strong typing matter and both ends are under our control. I avoid gRPC for browser-facing APIs unless we’re prepared for gRPC-Web and the extra setup. I prefer REST at the edge and gRPC internally in microservices: public or partner-facing APIs stay REST; service-to-service can be gRPC for efficiency. I don’t choose gRPC just for “it’s faster” if the team has no .proto experience and the latency requirement doesn’t justify the learning curve.

Trade-Offs & Failure Modes

REST sacrifices some performance and compactness; you gain universal support, caching, and simplicity. gRPC sacrifices browser-friendliness and “curl-ability”; you gain performance, streaming, and contract-first with protobuf. Both in one system (REST edge, gRPC internal) adds two stacks to maintain but is common. Failure modes: using gRPC for a public API and then struggling with browser support; using REST for high-frequency internal calls and then hitting latency limits; ignoring contract versioning (both need a strategy for breaking changes).

What Most Guides Miss

Most guides compare REST vs gRPC on features but don’t stress that client type is the first filter—browser or third party → REST (or gRPC-Web with effort); internal service → gRPC is often better. Another gap: streaming is a strong reason for gRPC (client/server/bidi streams) but is underused; many teams use request-response only and could stay with REST. Contract evolution (.proto and OpenAPI versioning) is rarely discussed; both need a policy for backward-compatible changes.

Decision Framework

  • If clients are browsers or external partners → Prefer REST; use JSON and HTTP caching.
  • If clients are internal services and you need performance or streaming → Prefer gRPC; use .proto and HTTP/2.
  • If you have both public and internal consumers → REST at the edge (gateway, BFF); gRPC between internal services.
  • For new internal service-to-service → gRPC if the team can maintain .proto; REST if simplicity and familiarity matter more.
  • For contract changes → Version .proto or API; support backward compatibility or document breaking changes.

Key Takeaways

  • REST for public APIs, browsers, and broad compatibility; gRPC for internal services, streaming, and performance.
  • Choose by client type first: browser/external → REST; internal + performance/streaming → gRPC.
  • REST at edge, gRPC internal is a common and valid split.
  • gRPC needs .proto and HTTP/2; avoid for browser-only unless you use gRPC-Web.
  • Plan contract versioning for both REST and gRPC.

Need architectural guidance for real-world .NET platforms? I offer consulting for .NET architecture, API platforms, and enterprise system design.

When I Would Use This Again — and When I Wouldn’t

I’d use REST again for any public or browser-facing API and for partner integrations where HTTP/JSON is the norm. I’d use gRPC again for internal .NET-to-.NET or service-to-service APIs where we need throughput or streaming. I wouldn’t choose gRPC for a public API without a clear need (e.g. streaming) and gRPC-Web or proxy in place. I also wouldn’t force gRPC on a team that has no protobuf experience and a tight deadline—REST is the safer default when in doubt.

services
Frequently Asked Questions

Frequently Asked Questions

What is REST?

REST (Representational State Transfer) is an architectural style using HTTP methods and URLs to expose resources. Responses are typically JSON. It is stateless, cacheable, and widely supported.

What is gRPC?

gRPC is a high-performance RPC framework using HTTP/2 and Protocol Buffers. It supports streaming and strong typing via .proto files.

What are Protocol Buffers?

Protocol Buffers (protobuf) are a binary serialisation format with a schema definition language (.proto files). They are compact and fast.

Is gRPC faster than REST?

Generally yes. Binary serialisation (protobuf) is smaller and faster to parse than JSON. HTTP/2 multiplexing reduces latency. Benchmarks show 5-10x improvement in some scenarios.

Can I use gRPC in browsers?

Not natively. Browsers do not support the HTTP/2 trailers gRPC requires. Use gRPC-Web with a proxy (Envoy) or middleware (ASP.NET Core Grpc.AspNetCore.Web).

When should I use REST over gRPC?

Use REST for public APIs, browser clients, when you need HTTP caching, or when interoperability is paramount.

When should I use gRPC over REST?

Use gRPC for internal service-to-service communication, high-throughput scenarios, streaming, or when you want contract-first development.

How do I version gRPC APIs?

Evolve .proto files backward-compatibly: add new fields (do not reuse field numbers), do not remove fields, deprecate instead. For breaking changes, create a new service version.

How do I handle errors in gRPC?

Throw RpcException with a StatusCode (e.g. NotFound, InvalidArgument, Internal). The client catches and handles based on the status code.

What is gRPC streaming?

gRPC supports three streaming modes: server streaming (server sends multiple messages), client streaming (client sends multiple messages), and bidirectional (both send). Use for real-time data, large result sets, or chat-like scenarios.

How do I secure gRPC?

Use TLS. gRPC over HTTP/2 typically requires TLS. Add authentication via interceptors (e.g. JWT token in metadata).

Can I use REST and gRPC together?

Yes. Many architectures expose REST at the edge (API Gateway) and use gRPC internally between microservices. The gateway translates.

What tools help debug gRPC?

Use grpcurl (command-line), Bloom RPC (GUI), or Postman (now supports gRPC). Reflection can be enabled for discovery.

What is the N+1 problem in gRPC?

Same as in REST: making many small requests instead of one batch. Use batch endpoints or streaming to avoid.

How do I add gRPC to an existing ASP.NET Core project?

Add Grpc.AspNetCore NuGet package, add .proto files to the project, implement service classes, and call MapGrpcService<T>() in Program.cs.

services
Related Guides & Resources

services
Related services