What are Request/Response APIs?
Request/Response APIs are the most common way for applications to communicate across process, network, or service boundaries. A client sends a request for information or an action, and a server returns a response containing the result.
This model is simple, predictable, and well-suited to workloads where a consumer knows when it needs data and expects an immediate answer. Web applications, mobile apps, dashboards, internal tools, and third-party integrations all commonly rely on request/response communication.
Common use cases include:
- CRUD applications
- Web and mobile backends
- Public developer platforms
- Internal business systems
- Reporting and analytics APIs
- Data access layers for enterprise applications
This article includes: REST, GraphQL, Falcor, OData.
How to choose?
- Simplicity, interoperability, and widespread ecosystem support -> REST
- Clients need flexible access to complex data -> GraphQL
- Efficient access to large, connected data graphs -> Falcor
- Standardization, querying capabilities, and enterprise integration are primary concerns -> OData
flowchart TD
A[Need a Request/Response API] --> B{Need a standardized query language<br/>for business data and integrations?}
B -->|Yes| O[OData]
B -->|No| C{Do clients need precise control<br/>over returned fields and shapes?}
C -->|No| R[REST]
C -->|Yes| D{Are you building around a<br/>single logical data graph with<br/>heavy client-side navigation?}
D -->|Yes| F[Falcor]
D -->|No| G[GraphQL]
Comparison
| Aspect | REST | GraphQL | Falcor | OData |
|---|---|---|---|---|
| Mental Model | Resources exposed through endpoints | Query a graph of data | Navigate a virtual JSON graph | Resources enhanced by a standardized query language |
| Data Fetching Flexibility | Low to Medium | High | High | Medium to High |
| Client Control | Limited by endpoints | High | High | Moderate |
| Complexity | Low | Medium to High | Medium to High | Medium |
| Caching | Excellent and well understood | More challenging | Good with graph-aware tooling | Good |
| Tooling | Extremely mature | Mature and growing | Smaller ecosystem | Strong enterprise ecosystem |
| Learning Curve | Low | Medium | Medium to High | Medium |
| Typical Use Cases | Public APIs, web services, CRUD systems | Frontend-heavy applications, aggregating multiple data sources | Rich data-driven applications with complex relationships | Enterprise systems, analytics, business applications |
| Strengths | Simplicity, scalability, broad adoption | Flexible queries, reduced over/under-fetching | Efficient graph traversal and data reuse | Standardized querying and interoperability |
| Weaknesses | Multiple requests for related data, endpoint proliferation | Increased operational and schema complexity | Smaller adoption and ecosystem | Can feel verbose and enterprise-oriented |
REST
REST stands for REpresentational State Transfer. It was introduced by Roy Fielding, one of the creators of HTTP. He published his PhD dissertation in 2000 explaining why the World Wide Web scaled so well. According to him, the web succeeded because it followed certain architectural constraints. This architectural style became known as REST.
REST offered a simpler model than the RPC-style communication common between systems at the time:
- Everything important is a resource
- Resources have URLs
- Standard HTTP methods operate on those resources
- Clients and servers remain loosely coupled
REST isn't a protocol. It's an architectural style.
Note: Strict REST, as originally defined, includes additional constraints such as HATEOAS (Hypermedia as the Engine of Application State). In practice, many APIs commonly referred to as "REST APIs" do not fully implement every constraint from the original definition — what most teams build and call "REST" is closer to resource-oriented HTTP APIs.
REST thinks in resources
A resource is simply a thing your system exposes:
- Users
- Books
- Orders
- Authors
Instead of:
createUser()
getUser()
deleteUser()
REST is:
POST /users
GET /users/123
DELETE /users/123
Examples
Create a user:
Request
POST /users
Content-Type: application/json
{
"name": "Alice"
}
Response
201 Created
{
"id": 123,
"name": "Alice"
}
Fetch a user:
Request
GET /users/123
Response
{
"id": 123,
"name": "Alice"
}
A representation of a resource is transferred during calls, not the resource itself.
Pros and Cons
| Pros | Cons |
|---|---|
| Simple and easy to understand using standard HTTP methods (GET, POST, PUT, DELETE). | Can lead to overfetching (receiving more data than needed). |
| Universally supported across browsers, servers, mobile apps, proxies, and CDNs. | Can lead to underfetching (multiple requests needed to gather related data). |
| Leverages existing HTTP infrastructure and tooling. | No built-in strong typing or schema enforcement. |
| Cache-friendly through HTTP caching headers and CDN support. | API contracts are often documented separately and validated at runtime. |
| Resource-oriented design maps naturally to CRUD operations. | Modeling complex business actions can become awkward. |
| Stateless architecture improves scalability and reliability. | Real-time communication is not natively supported. |
| Human-readable requests and responses simplify debugging. | Versioning strategies can become complicated over time. |
| Loose coupling between clients and servers allows independent evolution. | Deeply nested or relational data often requires multiple round trips. |
| Easy to test using browsers, curl, Postman, or similar tools. | Performance may suffer in chatty client-server interactions. |
| Large ecosystem, mature best practices, and widespread adoption. | Different teams often interpret REST principles differently, leading to inconsistent APIs. |
GraphQL
Facebook developed GraphQL internally in 2012 to tackle the difficulty of working with REST APIs as their mobile apps grew. Facebook open-sourced GraphQL in 2015.
Mobile clients often faced problems such as:
- Slow networks
- Limited bandwidth
- Multiple API requests for a single screen
- Different data requirements across platforms
REST forces the server to decide what data is returned. GraphQL lets the client decide. Instead of many endpoints, GraphQL provides a single endpoint, /graphql, through which all actions take place.
GraphQL thinks in queries
The client describes the exact structure of the response. The client requests fields, and the server assembles the response.
Example
A user has:
id
name
email
avatar
Client only needs name.
Query
query {
user(id: 123) {
name
}
}
Response
{
"data": {
"user": {
"name": "Alice"
}
}
}
Client needs name + email:
Query
query {
user(id: 123) {
name
email
}
}
Response
{
"data": {
"user": {
"name": "Alice",
"email": "alice@example.com"
}
}
}
Pros and Cons
| Pros | Cons |
|---|---|
| Eliminates overfetching | More complex backend architecture |
| Reduces underfetching and multiple requests | Caching is harder than REST |
| Strong schema and typing | Can suffer from N+1 query issues |
| Excellent tooling and developer experience | Query complexity must be controlled |
| Flexible for multiple client types | Overkill for simple CRUD systems |
| Self-documenting schema | Additional learning curve |
| Easier API evolution | Monitoring and performance tuning are harder |
Falcor
A note on relevance: Falcor is included here primarily for historical and conceptual understanding — it introduced ideas (like a unified virtual data graph) that are useful for building intuition. In practice, GraphQL became the dominant solution for flexible, client-driven data fetching.
Falcor was created by Netflix and open-sourced in 2015. Netflix applications needed data from many backend services:
Recommendations
User Profiles
Ratings
Viewing History
Search
Metadata
Building a screen often required multiple API calls. This created:
- Overfetching
- Underfetching
- High latency
- Complex frontend code
Falcor introduced a concept called the JSON Graph. Instead of exposing APIs as resources or functions, the server exposes a graph of interconnected data. The client requests paths through that graph.
Falcor thinks in graphs
Imagine application data as one giant object:
{
users: {},
movies: {},
ratings: {},
recommendations: {}
}
Clients don't call endpoints. They ask for paths inside the graph.
Example:
users[123].name
movies[42].rating
Core assumption: Application data is a graph, and clients should navigate that graph directly.
Example
Suppose the graph contains:
{
users: {
123: {
name: "Alice"
}
}
}
Client requests:
model.get([
"users",
123,
"name"
]);
Response:
{
"jsonGraph": {
"users": {
"123": {
"name": "Alice"
}
}
}
}
Pros and Cons
| Pros | Cons |
|---|---|
| Flexible graph-based data access | Steeper learning curve |
| Built-in client caching | Small ecosystem |
| Reduces API round trips | Limited adoption |
| Handles relationships naturally | GraphQL largely replaced it |
| Efficient for connected data | Fewer tools and community resources |
| Client-driven fetching | Complex mental model |
| Good Netflix-scale use cases | Rarely chosen for new projects |
OData
As organizations exposed more data over HTTP, developers repeatedly faced the same problems:
GET /users
GET /products
GET /orders
Every API invented its own way to support:
- Filtering
- Sorting
- Pagination
- Searching
- Expanding related data
Example:
GET /users?status=active
might work in one API, while another used:
GET /users?filter=status:active
There was no standard way to query data over HTTP. OData (Open Data Protocol) was introduced by Microsoft in 2007 and later standardized through OASIS. OData attempted to create SQL-like querying for HTTP APIs. Instead of every API inventing its own query language, clients could use a standard set of query operations.
Clarification — REST vs. OData: Traditional REST APIs usually expose fixed resource representations, meaning the server largely decides what shape a response takes. OData extends REST-style APIs with a standardized querying layer on top, giving clients more control over returned data (filtering, selecting fields, expanding relations) without changing the underlying request/response model.
OData thinks in queryable resources
Core assumption: APIs expose resources the same way REST does, but those resources are enhanced with a standardized query language — so clients can filter, sort, select fields, and expand relations without the server needing to build a custom endpoint for every variation.
Instead of:
GET /active-users
GET /users-by-country
GET /top-customers
you expose:
GET /users
and allow the client to specify the query. The server provides the data. The client decides how to filter it.
Example
Let's say:
GET /users
returns:
[
{
"id": 1,
"name": "Alice",
"country": "US"
},
{
"id": 2,
"name": "Bob",
"country": "IN"
}
]
Client requests:
GET /users?$filter=country eq 'US'
Response:
[
{
"id": 1,
"name": "Alice",
"country": "US"
}
]
Pros and Cons
| Pros | Cons |
|---|---|
| Powerful built-in querying | Query language complexity |
| Standardized filtering and sorting | Can expose expensive operations |
| Reduces endpoint proliferation | Tight coupling to data models |
| Metadata discovery support | Less flexible than GraphQL |
| Great for business datasets | Smaller ecosystem |
| Strong enterprise tooling | Rarely used for public APIs |
| HTTP and REST friendly | Requires careful security controls |
Key Takeaways
- Choose REST when you want simplicity, broad tooling support, strong HTTP caching, and your data access patterns are mostly straightforward CRUD.
- Choose GraphQL when clients have varied or evolving data needs, over/underfetching is a real problem, and you can support the added backend complexity.
- Choose OData when you need standardized, SQL-like querying (filtering, sorting, expanding relations) over business data, especially in enterprise or Microsoft-adjacent ecosystems.
- Choose Falcor rarely, if ever, for new projects — it's mainly useful for understanding the "unified data graph" mental model.
Top comments (0)