OData Walked So GraphQL Could Run (and Sometimes Trip)
OData has been around since before microservices were cool — quietly powering enterprise APIs while everyone else was busy reinventing REST for the 47th time (and still adding /v2 to the URL like it's 2009).
Then GraphQL shows up — all swagger and curly braces — promising perfect queries, no overfetching, and eternal happiness... as long as you don't mind writing resolvers for everything, debugging N+1 issues, and explaining to your PM why caching is now “a creative exercise.”
So... should you jump ship?
Short answer: not so fast. Long answer: it depends — yes, I said the consultant words, but unlike a consultant, I'm actually going to explain it, and I won't even invoice you.
What This Post Is Really About
This is not a “which one is better” post. This is about making good architectural decisions without falling for hype cycles — because both OData and GraphQL solve similar problems, they just do it in very different ways, and picking the wrong one for your situation is how you end up rewriting an API layer eighteen months from now while quietly resenting a conference talk.
OData — The Veteran That Still Hits Hard
OData (Open Data Protocol) is basically REST with query capabilities baked straight into the URL. Example:
GET /api/customers?$filter=firstName eq 'Orlando'&$expand=orders&$top=10Clean? Not really. Readable? Debatable — it looks like a database query that wandered into a URL and decided to stay. Powerful? Absolutely.
What Makes OData Special
The real magic: IQueryable gets translated directly into SQL.
[HttpGet]
[Route("api/customers")]
public IActionResult Get(ODataQueryOptions<Customer> options)
=> ODataCustomResult.ObtainResult(
options,
_dbContext.Customers.AsQueryable(),
_odataSettings);That means:
- Filtering happens in the database
- Joins happen in the database
- Paging happens in the database
Not in memory. Not in your API. Not in your nightmares at 3am.
Pros
- Extremely efficient (
IQueryableFTW) - Built for structured data
- Easy to secure and cache
- Minimal plumbing in .NET
Cons
- URLs look like you summoned a database wizard
- Mostly a Microsoft-ecosystem party
- Frontend devs will judge you — quietly, or loudly, depending on the frontend dev
Where OData Dominates
OData shines when:
- You have relational data
- You build internal APIs
- You need predictable performance
Think banking systems, ERP/CRM, reporting APIs — basically, serious business systems that don't care about hype and have never once been described as “disruptive.”
GraphQL — The Ambitious Overachiever
GraphQL flips everything. Instead of the server deciding the response, the client decides everything. Which sounds amazing... right up until the client asks for everything.
Example Query
query {
customers(filter: { firstName: "Orlando" }) {
customerId
firstName
orders {
totalDue
}
}
}C# Example (HotChocolate)
builder.Services.AddGraphQLServer()
.AddQueryType<Query>()
.AddFiltering()
.AddSorting();
public class Query
{
[UseFiltering]
[UseSorting]
public IQueryable<Customer> GetCustomers([Service] AppDbContext db)
=> db.Customers;
}What Makes GraphQL Powerful
- Single endpoint
- Strong schema
- Client-driven queries
But also: you are now personally responsible for everything the framework used to handle. Fun.
Pros
- Fetch exactly what you need
- Great for multiple clients
- Self-documenting
- Frontend teams love it, and will tell you so, repeatedly
Cons
- N+1 query problems (welcome to the jungle)
- Resolver complexity explodes over time, like interest on a payday loan
- Caching becomes... philosophical
Where GraphQL Wins
GraphQL is perfect when:
- You have multiple frontends
- You aggregate multiple services
- You need flexibility over predictability
Think mobile apps, dashboards, public APIs.
The Real Comparison
| Feature | OData | GraphQL |
|---|---|---|
| Query Model | URL-based | Query language |
| Execution | Server-driven | Client-driven |
| Performance | Predictable | Depends on you |
| Flexibility | Medium | High |
| Complexity | Low | Medium–High |
Architecture View
OData Flow
@startuml
actor Client
participant API
participant EF
database DB
Client -> API
API -> EF
EF -> DB
DB --> EF
EF --> API
API --> Client
@endumlStraight line. Minimal surprises. The kind of diagram you can explain in one breath.
GraphQL Flow
@startuml
actor Client
participant GraphQL
participant Resolver
participant EF
database DB
Client -> GraphQL
GraphQL -> Resolver
Resolver -> EF
EF -> DB
DB --> EF
EF --> Resolver
Resolver --> GraphQL
GraphQL --> Client
@endumlFlexible... but now you own the complexity, and the complexity knows where you live.
Real Production Pitfalls (This Is Where Things Break)
OData Pitfalls
- Over-exposing your data model
- Poor query limits → performance issues
- Letting users build “creative” queries
Translation: your database becomes a public playground, and the public did not read the rules sign.
GraphQL Pitfalls
- N+1 queries (the classic)
- Over-fetching disguised as “flexibility”
- Deep nested queries from hell
Translation: your DB cries silently, and monitoring is the only one who hears it.
Performance Reality Check
| Scenario | Winner |
|---|---|
| Simple CRUD | OData |
| Complex joins | OData |
| Multi-source aggregation | GraphQL |
| Client-specific payloads | GraphQL |
If your backend is DB-heavy → OData wins. If your frontend is chaotic → GraphQL wins. If both are true, congratulations, you have a normal company.
Anti-Patterns (Please Don't Do This)
OData Anti-Patterns
- Exposing the entire DB without restrictions
- Ignoring query limits
- Using OData for public APIs without controls
GraphQL Anti-Patterns
- One resolver per field (good luck scaling that)
- No batching (hello N+1, my old friend)
- Treating GraphQL like REST with extra steps
My Take (No Sugar Coating)
I still use OData more. Why? Because most enterprise problems are structured, predictable, and database-driven — and OData excels there, quietly, without a single conference keynote.
GraphQL? I use it when frontend teams need flexibility, or when data comes from multiple services and something has to play traffic cop.
The Hybrid Reality (The Real Answer)
The best architectures don't choose. They combine:
- OData → internal systems
- GraphQL → external layer
Even better: GraphQL on top of OData — a clean data layer underneath, a flexible API layer on top. Everybody wins. Nobody fights. Mostly.
Teaser: Custom Envelope v2
If you've read my previous OData post, you know I use a custom envelope pattern. Version 2 is coming with better serialization, performance improvements, and cleaner metadata handling — and yes, it plays nicely with hybrid GraphQL setups. Stay tuned.
Final Thoughts
The goal is not to follow trends. The goal is to build systems that actually work in production — a much less glamorous goal, with a much better on-call schedule.
Key Takeaway
- OData = efficiency + structure
- GraphQL = flexibility + control
The real skill is knowing when to use each.
Al revisar camisetas de fútbol para aficionados, conviene empezar por las diferencias de corte entre versión de jugador y de aficionado. Para evitar errores, merece la pena revisar si el precio corresponde a la versión seleccionada.
Happy coding!!!