Debunking the "POST only" API myth, explaining why it stems from a misunderstanding of API design principles, and clarifies the appropriate use cases for RESTful and RPC architectural styles.
Recently, a discussion about whether to design APIs using "POST only" caught my attention. After delving into this debate, I found that not only is the issue people are arguing about meaningless, but it also exposes many developers' misunderstanding of the essence of API design. Today, let's dive deep into the core ideas of API design and see why this debate shouldn't exist in the first place.
The misconception of "POST only"
Those developers who advocate using "POST only" to replace RESTful API specifications clearly haven't grasped the most important point of API design. Their arguments usually include:
- Simplifying design: One method can handle everything
- Security: POST parameters don't appear in the URL
- Flexibility: POST can send any data structure
At first glance, these arguments seem to make some sense. But in reality, this view confuses the choice of HTTP methods with API design styles, two different levels of issues. POST is just one method of the HTTP protocol, while REST is a style of API design.
The essence of API design
Before discussing specific API styles, we need to understand what the core purpose of API design is. A good API should be:
- Clear and understandable: Other developers (including your future self) should be able to intuitively understand the purpose of each endpoint
- Consistent: Follow certain specifications to reduce learning costs
- Extensible: Able to easily perform version control and functional expansion
- Efficient: Consider efficiency in terms of performance and resource utilization
RESTful API: More than just a choice of HTTP methods
RESTful API is just one of many API design styles, focusing on resources and operations on resources. Let's take a simple blog system as an example to see how RESTful API is designed:
- Get all articles:
GET /api/articles
- Get a specific article:
GET /api/articles/{id}
- Create a new article:
POST /api/articles
Content-Type: application/json
{
"title": "REST vs RPC",
"content": "This is the article content...",
"authorId": 12345
}
- Update an article:
PUT /api/articles/{id}
Content-Type: application/json
{
"title": "Updated title",
"content": "Updated content..."
}
- Delete an article:
DELETE /api/articles/{id}
In this example, we can see:
- The API is designed around the "article" resource
- Different HTTP methods are used to represent different operations
- The URL structure is clear, indicating the resource being operated on This design approach makes the API more intuitive and self-explanatory, making it easy for developers to understand the function of each endpoint.
RPC: Understanding the API style behind "POST only"
The goal of RPC (Remote Procedure Call) style API design is to make remote service calls look as simple as calling local functions.
Interestingly, those advocating for "POST only" may not realize that they are actually describing the RPC style.
Compared to RESTful APIs, RPC focuses more on the operation itself rather than the resource. This is why RPC-style APIs typically use a "verb + noun" form, such as getProduct(productId)
or createUser(userData)
.
In many RPC implementations, all operations are usually sent via POST requests to the same endpoint, with the specific operation and parameters specified in the request body. This is why the idea of "POST only" is actually closer to RPC than REST.
For example, an RPC-style "get product" request based on HTTP might look like this:
POST /api/rpc/getProduct
Content-Type: application/json
{
"productId": 12345
}
Modern RPC frameworks, such as gRPC, provide more powerful and efficient implementations. Let's use this as an example to demonstrate the RPC style:
First, we define the service and message format (using Protocol Buffers):
syntax = "proto3";
package blog;
service BlogService {
rpc GetArticle (GetArticleRequest) returns (Article) {}
rpc CreateArticle (CreateArticleRequest) returns (Article) {}
}
message GetArticleRequest {
int32 article_id = 1;
}
message Article {
int32 id = 1;
string title = 2;
string content = 3;
int32 author_id = 4;
}
message CreateArticleRequest {
string title = 1;
string content = 2;
int32 author_id = 3;
}
Then, using this service in a Node.js client is as simple as calling a local function:
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const PROTO_PATH = __dirname + '/blog.proto';
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const blog_proto = grpc.loadPackageDefinition(packageDefinition).blog;
// Convert gRPC method to Promise
function promisify(client, methodName) {
return (request) => {
return new Promise((resolve, reject) => {
client[methodName](request, (error, response) => {
if (error) reject(error);
else resolve(response);
});
});
};
}
async function main() {
const client = new blog_proto.BlogService('localhost:50051', grpc.credentials.createInsecure());
const getArticle = promisify(client, 'getArticle');
const createArticle = promisify(client, 'createArticle');
try {
// Get article
const article = await getArticle({ article_id: 12345 });
console.log('Retrieved article:', article.title);
// Create article
const newArticle = await createArticle({
title: 'gRPC vs REST',
content: 'gRPC is awesome for internal services...',
author_id: 67890,
});
console.log('Created new article with id:', newArticle.id);
} catch (error) {
console.error('Error:', error);
}
}
main();
In this RPC-style example, we can see:
- The service definition clearly lists all available operations (in this simplified example,
GetArticle
andCreateArticle
). - Each operation has clearly defined request and response types.
- The client code looks like calling a local asynchronous function, using
await
to wait for the result, which further hides the complexity of network communication. - There's no need to manually construct HTTP requests or parse JSON responses.
Although the underlying layer may still use HTTP/2 as the transport protocol, RPC frameworks (such as gRPC) provide developers with an abstraction layer that makes remote calls look and feel like local function calls.
Therefore, we can see that most of the debates about "POST only" and RESTful APIs should essentially be discussions about these two API styles: REST and RPC. However, the key is to recognize that these two styles each have their applicable scenarios, and the choice should be based on the specific needs of the project, not personal preference.
REST vs RPC: No absolute superiority or inferiority
Now that we understand the differences between REST and RPC, let's look at their respective applicable scenarios:
- REST is suitable for:
- Resource-oriented applications (such as content management systems, blog platforms, e-commerce websites)
- Scenarios that require good cache support (GET requests are naturally cacheable)
- Projects that want to leverage HTTP semantics (such as using appropriate status codes)
- Public-facing APIs that require good discoverability and self-description
- RPC is suitable for:
- Action-oriented applications (such as complex data processing operations, workflow control)
- Systems that require high performance and low latency (such as real-time trading systems)
- Communication between internal microservices (which may require more flexible parameter passing)
- When operations cannot be simply mapped to CRUD (Create, Read, Update, Delete) operations
The choice of style should be based on your specific needs. In some cases, you might even use a mix of these two styles within the same system to meet the needs of different parts.
Conclusion
- The core of API design lies in clarity, consistency, extensibility, and efficiency, not in sticking to a particular method or style.
- Both RESTful and RPC are mature API design paradigms. The choice between them should be based on project requirements, not personal preference.
- If you decide to use "POST only", then please design your API according to the RPC style. Like RESTful, it's a commonly used API specification in the industry, but it should be used in appropriate scenarios.
- Don't be confused by superficial technical details. What really matters is whether your API can effectively serve your users and business needs.
- When designing APIs, spend more time thinking about your resource model, business logic, and user needs, rather than obsessing over which HTTP method to use.
Let's shift our attention away from these pointless arguments and focus on designing truly excellent APIs. After all, technology is for solving problems, not creating them.
Top comments (8)
This is one of the most concise API architecture implementation breakdowns I've seen in a good long while. Well done!
I'd love to see your combined take on GraphQL.
Learnt more than what I expected before reading.
Thanks for clarity, bytheway, I think basing on your write up, one should look into this "post only" pattern coz it seems to me that it's one of those features that make grpc great, it would be awesome if we can achieve the same on REST too!
Also, consider leaving for us a demo illustrating how you used it and where you got the challenges. But otherwise, great write up.
Very true
Thanks!!!
Excellent!
Great writeup!
clair