REST, GraphQL oder OData: So triffst du die richtige API-Entscheidung
REST, GraphQL oder OData? Diese Frage kommt in fast jedem Projekt irgendwann auf. Vielleicht startest du gerade ein neues Produkt und willst von Anfang an den richtigen API-Stil wählen. Oder dein bestehendes REST-API stößt an Grenzen: Das Frontend braucht immer mehr Sonderendpunkte, das Backoffice will flexible Reports und ein neuer Partner verlangt eine standardisierte Schnittstelle.
In beiden Fällen lohnt sich eine bewusste Entscheidung statt eines Bauchgefühls. Dieser Artikel hilft dir, die Unterschiede zwischen REST, GraphQL und OData zu verstehen und die passende Wahl für dein Projekt zu treffen. Wir schauen dabei gezielt auf Patterns zum Lesen und Schreiben von Daten. Streaming und Subscriptions lassen wir bewusst außen vor.
Wie du den richtigen API‑Stil auswählst
Bevor du über Frameworks oder Libraries sprichst, kläre zuerst die wichtigen Fragen: Welche Clients konsumieren die API? Wie flexibel müssen Abfragen sein? Welches Tooling und welche Erfahrung bringt dein Team mit? Auch der API-Überblick in ASP.NET Core empfiehlt diesen Blick, auch wenn die Entscheidung natürlich nicht auf .NET beschränkt ist.
In der Praxis gibt es selten eine perfekte Antwort. Du willst schnell liefern, aber gleichzeitig eine API, die in einem Jahr nicht zur Bremse wird. Bei GraphQL bestimmt der Client, welche Felder er in der Antwort bekommt. Bei OData steht eine standardisierte Query-Sprache im Mittelpunkt. REST punktet dagegen mit einem klaren Ressourcenmodell und breiter Integrationsfähigkeit.
Arbeite deshalb mit diesen Kernfragen:
- Wer nutzt die API? Externe Partner, Browser-Frontend, Mobile-App oder interne Tools? Jeder Client hat unterschiedliche Anforderungen an Stabilität, Flexibilität und Dokumentation.
- Wie oft ändern sich die Datenschemas? Schnelle Innovationszyklen profitieren von GraphQL oder OData. Stabile, langfristige Verträge mit Partnern sprechen für REST.
- Wie komplex sind die Read-Szenarien? Einfaches CRUD? Komplexe Filter und Aggregationen? Datenlastige Reports?
- Wie wichtig ist HTTP-Caching? REST nutzt etablierte HTTP-Standards perfekt. GraphQL ist komplexer zu cachen. OData sitzt dazwischen.
- Welche Team-Expertise bringst du mit? Für REST brauchst du kein spezielles Wissen. GraphQL und OData erfordern gelernte Patterns.
Wenn du diese Perspektiven pro Use Case sauber beantwortest, wird die Stilfrage deutlich einfacher.
REST mit ASP.NET Core: Controller vs Minimal APIs
REST bleibt für viele Teams der Standard, weil das Modell für Lesen und Schreiben sehr klar ist: GET für Collections oder einzelne Ressourcen, POST für Create, PUT oder PATCH für Update und DELETE für Löschoperationen. Dazu kommt die etablierte HTTP-Semantik mit sauberen Statuscodes.
REST (Representational State Transfer) ist kein Protokoll, sondern ein Architekturstil, der von Roy Fielding in seiner Dissertation (2000) beschrieben wurde. Die Grundidee ist, Ressourcen über eindeutige URIs zu adressieren und deren Repräsentationen (z. B. JSON, XML) über standardisierte HTTP-Methoden auszutauschen. Wichtige Constraints sind u. a. Client-Server, Stateless, Cacheable, Uniform Interface, Layered System und optional Code-on-Demand. Diese Prinzipien fördern Skalierbarkeit, lose Kopplung und klare Semantik im Umgang mit HTTP.
HATEOAS (Hypermedia as the Engine of Application State) ist Teil der Uniform-Interface-Constraint: Antworten liefern Links und ggf. Aktionsbeschreibungen, mit denen Clients dynamisch die möglichen nächsten Schritte entdecken können. Im Praxis-Ökosystem gibt es verschiedene Hypermedia-Formate und Konventionen wie HAL, JSON-LD/Hydra, Siren oder Collection+JSON, die HATEOAS-Unterstützung strukturieren. Viele APIs nutzen stattdessen dokumentationsbasierte Ansätze (z. B. OpenAPI) und implementieren HATEOAS nur teilweise oder gar nicht — das vereinfacht Clients, kostet aber Potenzial bei Discoverability.
In ASP.NET Core hast du zwei übliche Umsetzungsformen. Controller mit [ApiController] geben dir starke Konventionen, automatische Validierungsfehler und ein konsistentes Fehlerformat über ProblemDetails, wie in der Web API Dokumentation. Minimal APIs sind schlanker, schnell aufzusetzen und mit MapGroup sowie TypedResults trotzdem strukturierbar, wie im Minimal API Leitfaden.
Wichtig ist hier weniger die Syntaxfrage, sondern die Teamfrage. Größere Teams mit klaren Domänengrenzen fahren oft gut mit Controllern. Kleine Teams oder vertikale Feature-Slices profitieren häufig von Minimal APIs.
// Controller-basiert: stark bei Konventionen und klaren API-Verträgen
[ApiController]
[Route("api/todos")]
public sealed class TodosController : ControllerBase
{
[HttpGet("{id:int}")]
public ActionResult<TodoDto> GetById(int id)
=> Ok(new TodoDto(id, "Artikel schreiben", false));
[HttpPost]
public ActionResult<TodoDto> Create(CreateTodoDto input)
{
var created = new TodoDto(42, input.Title, false);
return CreatedAtAction(nameof(GetById), new { id = created.Id }, created);
}
}
public sealed record TodoDto(int Id, string Title, bool IsDone);
public sealed record CreateTodoDto(string Title);
// Minimal API: schlank für kleine bis mittlere Endpunkt-Slices
var todos = app.MapGroup("/api/todos");
todos.MapGet("/{id:int}", (int id) => TypedResults.Ok(new TodoDto(id, "Artikel schreiben", false)));
todos.MapPost("/", (CreateTodoDto input) =>
{
var created = new TodoDto(42, input.Title, false);
return TypedResults.Created($"/api/todos/{created.Id}", created);
});
Achte in beiden Varianten früh auf Over-Posting-Schutz, DTO-Mapping, konsistente Fehlerobjekte und Versionierung.
Over-Posting-Schutz verhindert, dass Clients mehr Felder senden und damit unbeabsichtigt Domain-Daten überschreiben; binde daher explizit DTOs und nutze DataAnnotations statt direkter Domain-Bindung. DTO-Mapping entkoppelt den API-Vertrag von der Domain-Logik; setze auf klare Projektionen und erwäge Bibliotheken wie AutoMapper oder Mapster (oder manuelles Mapping) für Wartbarkeit. Konsistente Fehlerobjekte (z. B. ProblemDetails in ASP.NET Core) schaffen ein einheitliches Fehlerformat für Clients und reduzieren Integrationsfriktion. Und schließlich: Lege eine Versionierungsstrategie (Header-, URL- oder Query-basiert) früh fest, um Breaking Changes für bestehende Verbraucher zu vermeiden.
GraphQL mit Hot Chocolate: Flexible Queries und gezielte Mutations
GraphQL wurde ursprünglich bei Facebook entwickelt und ab etwa 2012 intern genutzt, bevor es 2015 als Open Source veröffentlicht wurde. Ziel war, mobile Clients effizientere und flexiblere Abfragen zu ermöglichen.
GraphQL ist besonders stark, wenn verschiedene Clients unterschiedliche Sichten auf dieselben Daten brauchen. Statt fester Response-Formate bestimmt der Client den benötigten Payload selbst. Das reduziert Overfetching (zu viele Daten in der Antwort) und Underfetching (zu wenige Daten, sodass weitere Requests nötig sind), wie im offiziellen Guide zu GraphQL Queries beschrieben.
Für Writes nutzt du Mutations. Diese sind explizit modelliert und werden auf Top-Level seriell ausgeführt, was geordnete Änderungsabläufe unterstützt, wie in den GraphQL Mutations Grundlagen erklärt. Das ist gerade für fachliche Commands hilfreich.
In .NET ist Hot Chocolate das bekannteste Framework dafür. Du registrierst Query- und Mutation-Typen und mapst den Endpoint über MapGraphQL().
builder.Services
.AddGraphQLServer()
.AddQueryType<Query>()
.AddMutationType<Mutation>();
app.MapGraphQL();
public sealed class Query
{
public IQueryable<Product> GetProducts([Service] AppDbContext db)
=> db.Products;
}
public sealed class Mutation
{
public async Task<Product> UpdateProductPrice(int id, decimal price, [Service] AppDbContext db)
{
var product = await db.Products.FindAsync(id) ?? throw new InvalidOperationException("Product not found");
product.Price = price;
await db.SaveChangesAsync();
return product;
}
}
query ProductsForList {
products {
id
name
price
}
}
mutation UpdatePrice {
updateProductPrice(id: 7, price: 14.99) {
id
name
price
}
}
Plane bei GraphQL immer Query-Cost-Limits, N+1-Vermeidung und eine passende Caching-Strategie ein: Query-Cost-Limits verhindern extrem teure Abfragen (z. B. via MaxDepth/MaxComplexity oder Cost-Analyser). N+1-Probleme bedeutet mehrere Datenbankabfragen für verschachtelte Daten. Das vermeidest du mit Batching (DataLoader), gezielten Projektionen auf IQueryable oder Eager Loading (Include). Für Caching eignen sich Persisted Queries/Query-Hashes, serverseitige Response-Caches oder feldbasierte Caches (z. B. auf DataLoader-/Repository-Ebene) kombiniert mit TTLs und Invalidations; bedenke dabei Auth- und Authorization-Szenarien für personalisiertes Caching.
OData: Standardisierte Query-Sprache auf HTTP
OData kommt von Microsoft und ist ein offener Standard für RESTful APIs mit einer starken Betonung auf Datenmodellierung und Abfrageflexibilität.
OData ist dann spannend, wenn du sehr datenlastige Read-Szenarien hast und eine standardisierte Query-Sprache auf HTTP brauchst. Mit $select, $filter, $expand, $orderby, $top und $skip können Clients sehr präzise steuern, was sie bekommen. Das ist vor allem in Business-Intelligence-nahen Kontexten (BI) stark, etwa für Reporting oder Datenanalyse.
Für Write-Operationen nutzt OData wie REST die bekannten HTTP-Methoden. Für partielle Updates ist PATCH mit Delta<T> ein übliches Muster.
builder.Services.AddControllers().AddOData(options =>
{
options.Select().Filter().Expand().OrderBy().SetMaxTop(100).Count();
options.AddRouteComponents("odata", GetEdmModel());
});
[Route("odata/[controller]")]
public sealed class ProductsController : ODataController
{
private readonly AppDbContext _db;
public ProductsController(AppDbContext db) => _db = db;
[EnableQuery]
public IQueryable<Product> Get() => _db.Products;
public async Task<IActionResult> Patch([FromRoute] int key, [FromBody] Delta<Product> delta)
{
var entity = await _db.Products.FindAsync(key);
if (entity is null) return NotFound();
delta.Patch(entity);
await _db.SaveChangesAsync();
return Updated(entity);
}
}
Wenn du OData ohne Einschränkungen bereitstellst, kann das Backend sehr schnell unter Last geraten. Begrenze deshalb erlaubte Query-Optionen, setze MaxTop, validiere Abfragen und beobachte die tatsächlichen Query-Kosten.
Datenzugriff und Mapping: EF Core, IQueryable und alternative Datenquellen
Unabhängig vom API-Stil gilt ein Prinzip fast immer: Entkopple Domain- und Application-Layer von der API-Fassade. Dann kannst du REST, GraphQL und OData parallel betreiben, ohne Business-Logik dreifach zu pflegen. Die API-Schicht ist dann Adapter, nicht Kernlogik.
Bei EF Core ist der Unterschied zwischen IQueryable und IEnumerable zentral. Solange du auf IQueryable bleibst, kann der Provider Queries serverseitig übersetzen, wie in den EF Core Querying Grundlagen beschrieben. Wenn du zu früh materialisierst, landest du schnell in teurer In-Memory-Auswertung.
public sealed class ProductReadService
{
private readonly AppDbContext _db;
public ProductReadService(AppDbContext db) => _db = db;
// Gemeinsames Read Model für alle API-Stile
public IQueryable<ProductListItem> QueryProducts()
=> _db.Products.Select(p => new ProductListItem(p.Id, p.Name, p.Price));
}
public sealed record ProductListItem(int Id, string Name, decimal Price);
// REST Controller konsumiert denselben Service
// GraphQL Query-Resolver konsumiert denselben Service
// ODataController konsumiert denselben Service
Für GraphQL solltest du zusätzlich Batching und DataLoader-Strategien einplanen. Für OData brauchst du Guard Rails rund um EnableQuery. Und wenn Daten aus einem Data Warehouse (DWH), Legacy APIs oder externen Services kommen, wird gezielte Projektion wichtiger als ein rein ORM-getriebener Ansatz.
Hybrid-Ansätze: Wann die Kombination mehr Wert liefert
In vielen Produkten ist ein Hybrid-Ansatz die beste Lösung. Nicht weil man sich nicht entscheiden kann, sondern weil unterschiedliche Nutzergruppen unterschiedliche Stärken brauchen. Eine robuste Partnerintegration hat andere Anforderungen als ein datenintensives Backoffice oder ein Mobile-Frontend mit wechselnden Views.
Auch wenn ein Mix-Betrieb eher die Ausnahme ist, kann eine Mischung aus REST und GraphQL oder REST und OData sehr gut funktionieren. REST bleibt die stabile Basis für CRUD-Operationen und Integrationen, während GraphQL oder OData flexible Read-Szenarien abdeckt. Ein anderes Pattern ist REST als Integrationsbasis plus OData-Read-Kanal für Excel oder Power BI Self-Service, was durch den OData Feed in Power Query gut unterstützt wird. Und intern kannst du OData für Backoffice-Oberflächen einsetzen, während extern REST stabil bleibt.
Technisch funktioniert das gut, wenn alle Fassaden denselben Application Layer nutzen. Organisatorisch brauchst du dafür klare Ownership-Regeln, konsistente Authentifizierung und Autorisierung sowie ein einheitliches Fehler- und Audit-Modell.
Querschnittsthemen für alle drei Ansätze
Egal welchen API‑Stil du wählst, ohne Cross‑Cutting Concerns zu beachten, wird der Betrieb schwierig. Dazu gehören Versionierung, Sicherheit, Performance, Observability und Testing. Für Versionierung kannst du in ASP.NET Core etablierte Patterns über aspnet-api-versioning nutzen, inklusive OData-Support. Für GraphQL und OData solltest du früh über Breaking Changes nachdenken, z. B. durch Deprecation-Mechanismen, Versionierung auf Schema-Ebene oder klare Kommunikationsprozesse mit Clients.
Sicherheit ist mehr als ein Login. Definiere Authentifizierungs- und Autorisierungsschemata (z. B. OAuth2/OIDC für Benutzer, JWT für Maschinen), implementiere policy‑basierte sowie rollen-/claims‑basierte Regeln und ergänze bei Bedarf Autorisierung auf Feldebene bzw. je Eigenschaft (Property‑Level Authorization). Begrenze Query‑ und Payload‑Größen, validiere Eingaben strikt und schütze vor Over‑Posting. Ergänze TLS, sichere Header (HSTS, CSP), Rate‑Limiting und Audit‑Logging. In ASP.NET Core ist außerdem die Reihenfolge in der Middleware‑Pipeline relevant (Authentifizierung vor Autorisierung, Validierung vor Business‑Logik); siehe Authentication Dokumentation.
Performance braucht konkrete Leitplanken und Messgrößen: Wähle geeignete Paging-Strategien (Offset vs. Cursor/Keyset), setze sinnvolle Default- und Max‑Page-Größen und begrenze erlaubte Query‑Optionen (z. B. OData MaxTop, GraphQL MaxDepth/Complexity). Nutze Caching auf mehreren Ebenen (CDN/HTTP-Caching, serverseitiges Fragment- oder Feld‑Caching, DataLoader-Caches), vermeide N+1 durch Eager Loading/Batching, projiziere nur benötigte Felder und sorge für passende DB‑Indizes. Ergänze Timeouts, Retry-/Backoff-Strategien, Circuit Breaker und asynchrone Verarbeitung für lange laufende Tasks. Messt Latenzen (p50/p95/p99), Fehlerquoten und DB‑Query‑Kosten kontinuierlich.
Wie bei allen anderen Projekten auch, spielen Testing und Observability eine wichtige Rolle. Für alle API-Stile helfen API-Tests und ähnliches, um zu prüfen, ob alles noch sauber funktioniert. Ergänze strukturierte Logs, verteiltes Tracing (z. B. OpenTelemetry) und Metriken/Alerts, damit Breaking Changes, Performance‑Regressionen und Sicherheitsvorfälle frühzeitig auffallen.
public sealed class ApiGovernanceOptions
{
public int RestRateLimitPerMinute { get; init; } = 120;
public int GraphQlMaxDepth { get; init; } = 8;
public int GraphQlMaxComplexity { get; init; } = 200;
public int ODataMaxTop { get; init; } = 100;
public string[] SupportedApiVersions { get; init; } = ["1.0", "2.0"];
}
builder.Services.Configure<ApiGovernanceOptions>(builder.Configuration.GetSection("ApiGovernance"));
Welcher API‑Stil passt zu deinem Produkt?
Wenn dein Produkt stark integrationsgetrieben ist, ist REST oft der stabilste Startpunkt. Wenn Frontends viele unterschiedliche Datensichten brauchen, ist GraphQL für Reads häufig die bessere Wahl. Wenn Analytics und tabellarische Datenexploration im Mittelpunkt stehen, liefert OData mit standardisierten Query-Optionen oft den größten Hebel.
In der Praxis gewinnt selten ein Stil allein. Entscheidend ist, dass dein API‑Stil zu Teamstruktur, Betriebsmodell und Produktzielen passt. Wähle deshalb nicht nach Trend, sondern nach Ownership, Governance und dem tatsächlichen Nutzungsverhalten deiner Clients.
Starte klein mit einer klaren Hypothese, miss Nutzung und Last, und erweitere dann gezielt. So vermeidest du einen Big-Bang und entwickelst deine API-Landschaft kontrolliert weiter.
Wie triffst du heute die API-Entscheidung zwischen REST, GraphQL und OData in deinem Produkt? Schau gerne bei uns auf LinkedIn vorbei und diskutiere mit.