Building Blocks Intermediate 5 min read

API Styles in .NET: REST vs gRPC vs GraphQL

How to pick between REST, gRPC, and GraphQL in .NET, when a BFF helps, and what each style actually costs in latency and developer time.

Table of contents
  1. When does the API style actually matter?
  2. What numbers should I budget for the choice?
  3. What does the minimal architecture look like?
  4. What does the .NET 10 wiring look like?
  5. How do I avoid the classic API-style mistakes?
  6. What failure modes does each style introduce?
  7. When should you skip the API-style debate?
  8. Where should you go from here?

The API style choice gets decided in the first design meeting and lives in every line of client code afterward. Pick wrong and the team will work around it for years. This chapter breaks the choice into the three that matter for .NET - REST, gRPC, GraphQL - plus the BFF pattern that resolves a specific aggregation pain.

When does the API style actually matter?

Three signals.

Many clients, different shapes. If web wants the user's display name and mobile wants the same plus profile photo and partner integrations want a stripped down ID-only view, REST endpoints multiply. GraphQL collapses them.

Tight internal latency budget. Service A calls Service B 50 times to render a request. Each call adds JSON parsing and HTTP overhead. gRPC's binary Protobuf cuts the per-call cost ~3-5x and HTTP/2 multiplexes them on one connection.

External developer experience matters. A public API for partner integrations is read by humans with curl. JSON wins; Protobuf is opaque without tooling.

If none of these dominate, REST + JSON is the right default. The remaining 80% of .NET services never need anything else.

What numbers should I budget for the choice?

Style       Payload size       Encode/decode      Browser-friendly
REST+JSON   1x baseline        ~50 µs             yes
gRPC        ~0.3-0.6x          ~10-20 µs          no (needs grpc-web)
GraphQL     varies (custom)    ~50-100 µs         yes

For per-call latency, the network dominates everything for any hop > 1 ms. Switching from REST to gRPC saves ~30 µs of CPU; if your service is across the internet at 50 ms RTT, that is invisible. gRPC's win shows up in the data centre at <1 ms RTT, where the CPU saving matters.

What does the minimal architecture look like?

flowchart LR
    Web[Web client] -->|REST+JSON| Edge[ASP.NET Core API]
    Mobile[Mobile client] -->|REST+JSON| Edge
    Edge -->|gRPC| SvcA[Inventory service]
    Edge -->|gRPC| SvcB[Pricing service]
    Edge -->|gRPC| SvcC[User service]
    Edge --> DB[(Postgres)]

External clients speak REST+JSON; internal services speak gRPC. The edge service translates - one REST endpoint backed by three internal gRPC calls. This is the most common shape in mature .NET microservice stacks and it gives you both ergonomics where it matters.

What does the .NET 10 wiring look like?

REST is built into ASP.NET Core - no setup. gRPC needs a .proto file plus the package:

// inventory.proto - shared contract
service Inventory {
  rpc GetStock (StockRequest) returns (StockReply);
}
message StockRequest { string sku = 1; }
message StockReply   { int32 quantity = 1; bool available = 2; }
// Server side
builder.Services.AddGrpc();
app.MapGrpcService<InventoryGrpcService>();

public class InventoryGrpcService(IStockRepo repo) : Inventory.InventoryBase
{
    public override async Task<StockReply> GetStock(StockRequest req, ServerCallContext ctx)
    {
        var qty = await repo.GetQuantityAsync(req.Sku, ctx.CancellationToken);
        return new StockReply { Quantity = qty, Available = qty > 0 };
    }
}

// Client side - generated client, used like a normal service
builder.Services.AddGrpcClient<Inventory.InventoryClient>(o =>
{
    o.Address = new Uri("https://inventory:5001");
});

public class CheckoutService(Inventory.InventoryClient inv)
{
    public async Task<bool> CanFulfill(string sku)
    {
        var reply = await inv.GetStockAsync(new StockRequest { Sku = sku });
        return reply.Available;
    }
}

GraphQL with Hot Chocolate looks similar - schema-first or code-first, mapped to your domain types, with built-in DataLoader to solve the N+1 problem. The full example fits in any Hot Chocolate quickstart and is too long to inline here.

How do I avoid the classic API-style mistakes?

Four mistakes I keep seeing in code reviews:

What failure modes does each style introduce?

Chapter 13 instruments all three uniformly through OpenTelemetry's HTTP/gRPC support.

When should you skip the API-style debate?

When traffic is low and the team is small. A single REST API on ASP.NET Core handles 10K QPS comfortably. The gRPC win matters at service mesh scale; the GraphQL win matters at multi-platform scale. A startup with one API and one mobile app gets nothing from either beyond complexity. Stay with REST + JSON until the QPS estimate or the multi-client pain forces a real change.

Where should you go from here?

Next chapter: Elasticsearch in .NET apps - when LIKE queries on Postgres run out of road and you need a real search engine. After that, auth styles closes out the building-blocks group.

Frequently asked questions

Why is REST still the default?
Three reasons: every browser, mobile SDK, and integration platform speaks HTTP+JSON natively; HTTP caching (CDN, reverse proxy, browser) is free; debugging works with curl, Postman, browser devtools. gRPC and GraphQL each ask the team to give up one of those for a concrete benefit. The benefit must be real - and for an external API it usually isn't.
When does gRPC pay for the complexity?
Internal service-to-service traffic where the latency budget is tight (under 10 ms p99) and the schema is shared across teams. The Protobuf contract acts as documentation, the generated client cuts boilerplate, and HTTP/2 streaming handles long-running operations. Outside that - external APIs, browser clients, occasional integrations - REST stays better.
Is GraphQL worth it for a .NET backend?
Yes if (1) you have multiple client types (web, mobile, partner) wanting different shapes of the same data, (2) the team can invest in schema management and N+1 prevention, and (3) the backend already speaks a relational graph well. Hot Chocolate is the canonical .NET implementation and is genuinely good. For a single-client app, REST is simpler.
What is a BFF and when do I add one?
Backend-for-frontend - an aggregation layer between one specific client and many backend services. Add a BFF when the client (often mobile) makes 10 calls to render one screen. The BFF makes those calls server-side, returns one tailored payload, and reduces battery + bandwidth. Each major client gets its own BFF; sharing a BFF across iOS and Web defeats the purpose.