Hot take: Your API doesn't need PUT, PATCH, DELETE, or any of that academic nonsense.
The REST Circus 🎪
Let's be honest about REST in 2025. You've probably worked with APIs like this:
The "Pure" REST API:
GET /users/123 // Get user
POST /users // Create user
PUT /users/123 // Replace user
PATCH /users/123 // Update user
DELETE /users/123 // Delete user
The "We Just POST Everything" API:
POST /api/getUser
POST /api/createUser
POST /api/updateUser
POST /api/deleteUser
The "Hybrid Chaos" API:
GET /users/123 // Sometimes works
POST /users/login // Makes sense
GET /users/delete/123 // WHAT?!
Sound familiar? Every API implements REST differently because REST is fundamentally broken for modern web development.
The 2025 Reality Check ✨
Here's what actually happens in modern apps:
📱 Modern Frontends Handle Their Own Navigation:
- React/Vue/Angular: Client-side routing with React Router, Vue Router
- Flutter/React Native: Built-in navigation, no HTML needed
- Mobile Apps: Just API calls, zero server-rendered pages
- Electron Apps: Local routing, only need data endpoints
🤖 What Your Backend Actually Needs:
POST /api/users ← React calls this for user data
POST /api/products ← Flutter calls this for product list
POST /api/auth ← Mobile app calls this for login
POST /api/analytics ← Dashboard calls this for charts
That's it. Your backend is just a data API. No HTML. No server-side routing. Just endpoints that return data.
The only time you need GET is for:
- Static assets (CSS, JS, images)
- SEO landing pages (if you even have them)
- Health checks and monitoring
Why PUT/PATCH/DELETE Are Theater 🎭
The REST method circus tries to encode semantics into HTTP verbs:
- "Is this operation idempotent?"
- "Are you replacing or updating?"
- "What about partial updates?"
- "Should DELETE return the deleted resource?"
Nobody cares. It's code talking to code. Just be explicit:
# Instead of PUT /users/123
action = "replace_user"
id = 123
name = "John Doe"
email = "john@example.com"
# Instead of PATCH /users/123
action = "update_user"
id = 123
email = "newemail@example.com"
# Instead of DELETE /users/123
action = "delete_user"
id = 123
The intent is crystal clear without memorizing HTTP semantics.
Enter TOML: The JSON Killer 🚀
JSON has quote hell:
{
"user": {
"name": "John Doe",
"preferences": {
"notifications": true,
"theme": "dark"
}
}
}
TOML is clean:
action = "create_user"
[user]
name = "John Doe"
[user.preferences]
notifications = true
theme = "dark"
Benefits:
- ✅ Human readable
- ✅ No quote escaping nightmares
- ✅ Comments supported
- ✅ Simpler parsing than YAML
- ✅ Self-documenting with action fields
The Solution: GET/POST + TOML 💡
New rule:
- GET for anything humans access directly
- POST for all API calls, with TOML and action fields
Example API:
# POST /api/users
action = "get"
id = 123
Response:
status = "success"
[user]
name = "John Doe"
email = "john@example.com"
created_at = 2025-06-13T10:30:00Z
More examples:
# Create user
action = "create"
name = "Jane Smith"
email = "jane@example.com"
# Update user
action = "update"
id = 456
email = "newemail@example.com"
# Delete user
action = "delete"
id = 789
# Complex operations
action = "send_welcome_email"
user_id = 123
template = "premium_welcome"
Why This Works 🎯
Consistency: Every API uses the same pattern - POST with action field
Simplicity: No HTTP method confusion, no semantic debates
Clarity: The action field tells you exactly what's happening
Evolution: Easy to add new actions without breaking existing code
Debugging: TOML is human-readable in network tabs
Caching: Use HTTP headers and cache-control for the few APIs that need it
Real World Example: Laravel Pagination Hell 🔥
Let's see how Laravel's "convenient" pagination compares to our TOML approach:
Laravel's Bloated Response:
{
"current_page": 1,
"data": [
{
"id": 1,
"title": "My Post",
"content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit...",
"author": {
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"bio": "Software developer...",
"avatar": "https://...",
"followers_count": 1337
},
"comments": [
{
"id": 456,
"content": "Great post!",
"author": {
"id": 789,
"name": "Jane Smith",
"email": "jane@example.com"
}
}
],
"tags": ["laravel", "php", "web"],
"created_at": "2025-06-13T10:30:00Z"
}
// ...14 more posts with ALL this nested data
],
"first_page_url": "http://example.com/api/posts?page=1",
"from": 1,
"last_page": 67,
"last_page_url": "http://example.com/api/posts?page=67",
"links": [
{"url": null, "label": "« Previous", "active": false},
{"url": "http://example.com/api/posts?page=1", "label": "1", "active": true},
{"url": "http://example.com/api/posts?page=2", "label": "2", "active": false}
],
"next_page_url": "http://example.com/api/posts?page=2",
"path": "http://example.com/api/posts",
"per_page": 15,
"prev_page_url": null,
"to": 15,
"total": 1005
}
Look at all that bloat:
- 🗑️ URLs nobody uses (
first_page_url
,last_page_url
) - 🗑️ Links array for server-side pagination
- 🗑️ Nested author objects repeated everywhere
- 🗑️ Full comment threads loaded upfront
- 🗑️ Massive content strings
- 🗑️ Metadata that React doesn't need
TOML + Object References Approach:
# Initial feed request
action = "get_feed"
user_id = 123
limit = 15
Clean response:
status = "success"
post_ids = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
author_ids = [123, 456, 789] # Unique authors only
has_more = true
next_cursor = "eyJpZCI6MTUsInRpbWUiOjE2ODk="
Load post content when needed:
action = "get_posts"
ids = [1, 2, 3, 4, 5]
Load authors when displaying:
action = "get_users"
ids = [123, 456, 789]
Load comments when expanded:
action = "get_comments"
post_id = 1
limit = 5
The results:
- Laravel: 50KB response with duplicate data
- TOML: 2KB response with smart references
- Laravel: Everything loaded upfront (even if never viewed)
- TOML: Load exactly what the user sees
- Laravel: Cache nightmare (invalidate everything if one comment changes)
- TOML: Surgical caching (posts, authors, comments cached separately)
This approach perfectly matches modern UX patterns:
- 📱 Infinite scroll
- 💬 Lazy-loaded comments
- ⚡ Prefetching
- 🎯 Optimistic UI updates
"But What About Semantics?" 🤔
REST purists will cry: "You're losing HTTP semantics!"
Good. HTTP semantics are:**
- Inconsistently implemented
- Academically interesting but practically useless
- A source of endless bikeshedding
- Solving problems that don't exist in 2025
Modern web development has exactly one HTTP use case:
Code exchanging data → POST
Even "browsing" is handled by React Router, not server routes.
Everything else is architectural masturbation.
"But My Frontend Needs GET Routes!" 🙄
No, it doesn't.
Your React app uses React Router:
// This is all client-side, zero server involvement
<Route path="/users/:id" component={UserProfile} />
<Route path="/dashboard" component={Dashboard} />
Your Flutter app has built-in navigation:
// Native navigation, no HTML needed
Navigator.pushNamed(context, '/userProfile');
Your mobile app just calls APIs:
// Just fetch data, handle navigation locally
userService.fetchUser(id: 123)
Your backend's only job: Return data when asked.
The Migration Path 🛤️
You don't need to rewrite everything. Start small:
- New APIs: POST + TOML with actions
- Legacy APIs: Keep them running
- Frontend: Gradually migrate to the new pattern
- Documentation: Much simpler - just document the actions
# Your API becomes self-documenting
action = "user_login"
email = "user@example.com"
password = "secret"
remember_me = true
No more explaining the difference between PUT and PATCH to new developers.
Conclusion: Keep It Simple 🏆
Web development has been overcomplicating HTTP for decades. REST promised simplicity but delivered architectural bikeshedding.
Modern apps in 2025 need:
- POST for data exchange
- TOML for clean, readable payloads
- Action fields for explicit intent
Your React app doesn't care about HTTP semantics. Your Flutter app doesn't need RESTful URLs. Your mobile app just wants data.
Stop arguing about HTTP methods. Start building.
What do you think? Ready to ditch the REST circus for something that actually works? Let me know in the comments! 👇
Top comments (6)
@xwero
Hey David! You're missing the reality - every major framework already ditched strict REST. GraphQL makes everything POST. gRPC doesn't even use HTTP methods. WebSockets are bidirectional. Modern React apps handle routing client-side.
And JSON being 'built-in' only matters in browsers. Your Python backend still runs
json.loads()
, Rust usesserde_json
, Go runsjson.Unmarshal()
. Every language builds custom deserializers anyway.So if we're parsing text formats regardless, why not parse something that serves 2025 needs? My approach sends 2KB of references instead of 50KB of duplicated JSON blobs. Load exactly what you need, when you need it.
The post-author mapping is simple - posts include
author_id
, load authors separately when displaying. Same normalized pattern Redux already uses.Your concern about HTTP being the long game doesn't contradict anything... it's just the damn protocol. Look, REST is built on top of HTTP, not the other way around. You can still send SOAP XML bloat with proper REST API scheme and guess what? It's still terrible.
HTTP will outlive REST just like it outlived SOAP. The protocol stays, the patterns evolve. My TOML approach still uses HTTP - just POST with better payloads instead of forcing semantics into verbs that don't match what modern apps actually do.
Anyway....not all are dreamers in the end.
I don't think in frameworks, I think in servers, browsers and applications.
Graphql biggest claim is to be less rigid than REST. The thing is you can make REST calls less rigid, you don't need a layer on top for the configuration you need to do anyway.
Like REST, (g)RCP and websockets are not silver bullets. You need to use them in the right context.
Other languages have JSON functions because of the javascript monopoly in the browsers. This should be an eyeopener to prefer JSON over TOML,
Your TOML solution is a false equivalence, because it is not pushing the same data. It is possible to do the same thing in json, but for both solutions it will require more roundtrips and that something you want to avoid.
TOML is only shaving off a few bytes.
REST is build on the strength of HTTP methods. If they are not implemented well, that is developer error. The methods are not there because of REST, they are a part of the protocol.
That is why I mentioned you are fighting the foundations.
I never use JSON. If the data structure is complex I use XML and if the structure is simple I use INI-like structure. I also only use INI-like for data transfer.
You seem to be an expert in front-end development.
I would like to ask you to comment on this technology.
dev.to/elanatframework/series/31742
What a rage bait article. But I had a good laugh.
React and Flutter are not meant for the long haul, HTTP semantics are. Why would you fight against the foundations of the internet? Look what happens with buildings when builders don't take foundations seriously.
For json it is build-in javascript, and javascript is the only browser language. So why use an arbitrary config format over an javascript data object?
For other applications the goal should be to keep the data as small as possible, not to make it human readable.
If Post requests and TOML are your thing, go for it. But don't expect many people to follow.
I would have given you one thing, data repetition in json. But that is fixable.
Only your fix forgot to add a post-author mapper, So your solution misses an essential piece of the information.
It's an interesting idea, and there's a lot to unpack, here. This response admittedly became a bit rambling, so I apologize for that. There was a time about a decade ago I was on board for REST. And it made sense at the time in the limited case I was working on. But just like database normalization, there are times when you need to denormalize data for efficiency or validity, and REST is a lot like dealing with normalized data. But so is your "replacement".
Like-for-like Replacement
REST isn't replaced with TOML in your example, but with an RPC-style API (one endpoint and an
action
parameter to define the function/method called on the other end). TOML replaces JSON in your example, but REST predates JSON and the two are not actually tied together. You can do REST with TOML if you want, and plenty of people use RPC-like services with JSON. So there's a lot of inconsistency in the comparisons.POST Problems
POST comes with significant challenges to caching, so GET requests are usually still necessary to provide scale. Caching headers on POST do allow limited caching but cannot give you the benefit of CDN caching easily. You don't get "surgical caching" at the API layer if every unique action is a parameter on the same endpoint; you have to re-create a caching strategy yourself rather than inheriting the robust – if slightly inconvenient – one from HTTP.
Most of the reason we still have "RESTish" APIs is for differentiating idempotent requests for caching. Even the RPC-like endpoints – using
/getUsers
is basically identical to callingaction = "get_users"
– often still have GET for obtaining data, unless they have APIs that potentially include non-public information (NPI) like account numbers or things that shouldn't end up in in the URL in log files. As soon as scale starts to kick in, a proxy, reverse proxy, squid, CDN, or even just nginx in front of your Node server can cache GET requests pretty much by default. But not if you insist on POST for everything.There are also the HEAD and OPTIONS methods, which you've likely forgotten when suggesting we eliminate the others. We aren't usually exposed to them directly as developers. The useful methods depend on the API used (REST, RPC, WebDAV, etc.). Even if they aren't used in your interface, they continue to make sense for WebDAV or the more basic file-level actions that closely match the HTTP 1.1. spec.
Application !== API
Most of your complaint seems to be about the Laravel response but you have lumped in JSON with REST and Laravel, so it's not a realistic comparison. None of this is a complaint about JSON, nor really about the API, just about the application layer.
The Laravel solution is not perfect. It incorporates potentially duplicate information, but it attempts to represent a page of content in one request. Your assumption about the way content should be loaded and presented which doesn't align with the decisions that were relevant when Laravel's design was implemented, and likely still are for plenty of use cases, just not the way you've decided to think about content loading. This can vary by design intent, use case, and scale. As an example, infinite scroll isn't always best.
You say the Laravel response is 50K vs 2K, but you left out the size of all your additional requests. Your first response doesn't have the content to render presentation to the user.
Duplicate Data
The problem of nested object duplication has been solved several different times. I've not dealt with it personally but I understand Falcor was designed with this relationship management in mind? Something like this came up recently in an article about RSC and a reaction video from Primagen. In a RESTful interface, we wouldn't provide duplicate data. It actually looks a lot like your design:
A truly RESTful interface would make you call each of the posts and users separately, though. Which is part of the reason nobody implements truly RESTful interfaces. I agree with you that they don't make sense as an API for complex modern applications.
Round Trips Aren't Free
Your solution retains the primary defect of REST interfaces: multiple requests to obtain data necessary to render content. This is basically why GraphQL exists, because making multiple requests is a major bottleneck in real-world performance. This is the same reason RSC exists. It's much faster to make five DB requests from the server with a 1ms round-trip for one HTTP request than five HTTP requests with a 20-1000ms round trip each.
Multiple requests may seem trivial during development, but someone on the other side of the world may have a ~900ms round trip – something my day job deals with. While the Laravel solution is larger and contains duplicate data, it can be substantially faster than making multiple requests in serial.
get_feed
- Trip #1 and nothing to render. We only have a list of IDs.get_posts
- Trip #2 and not ready to render because we don't have author data.get_users
- Trip #3 and finally ready to render, as long as you didn't want comments.When you add onto that the cost of HTTP congestion windows (slow start) and the significant benefits that HTTP compression provides on repetitive data structures, the design and performance cost of transmitting duplicate information is usually far lower than the work to make multiple requests and then map it all back together.
Splitting up data into normalized portions can make sense. That's usually how it's stored, but not necessarily how it's consumed.
JSON v TOML
TOML is fine, so long as you never have to represent arrays or nested data. Then it's kind of a nightmare to read because it's all handled as headers at the same indentation depth, so you can't easily see the relationship or hierarchy. But there's nothing specifically wrong with TOML. JSON is de facto because we get it "for free" in JavaScript, but there's nothing that ties us to it especially. We had XML before that. I never really liked it; it doesn't represent arrays well.
It's All Bikeshedding
At one of my pasts jobs we used a CDN and ESI on JSON responses to allow us to invalidate sections of a high-demand response, providing exactly the type of targeted cache invalidation that you complained the Laravel-style response is incapable of. This allowed edge servers to provide a complex response object to quickly serve all the content necessary for a page render faster than we could return it, and substantially reduce server load, using standard HTTP caching techniques. It wasn't typical, and these kind of solutions seemed wild to me before I worked at that scale. But I was coming from sites with thousands of daily requests moving into a world where thousands weren't even a rounding error.
From a different perspective it probably looked like we were fixated on a trivial point rather than the real problem. Maybe we could have redesigned all of that to serve different objects or structures and end with an even better solution. Or maybe we were improving an existing system to avoid the costs and risks of a redesign. Hard to say which way is correct.
Your specific use case with one endpoint, always using POST, with an RPC-like
action
, serving TOML instead of JSON is fine. It probably makes you happy, too. Which is good. But it's not The Answer™. You decided on an action parameter in your request to represent different functions rather than using different routes to represent different functions. TOML is more convenient for you reading the API responses, but we're not the real API consumer. It's helpful for troubleshooting, but that's why the JSON prettifier is built into Dev Tools...we can make tools to improve the developer experience without changing the platform.You are bikeshedding along with the rest of us. Welcome!
Conclusion
There's nothing wrong with not knowing why someone would want to use a different pattern or technology, or being dissatisfied with how one solution meets your use case. I personally hate Enterprise Java, but even in that I recognize it's mostly idiomatic issues, and not the language at fault.
But saying that everyone else is wrong or something is "architectural masturbation" because you don't agree suggests an ignorance of the scope and variety of goals and constraints out there. I hope you'll eventually look back on your post with the experience to recognize this.
the API hot take we didn’t know we needed 🔥