As a REST enthusiast I was excited to read this and learn a new perspective but this section
> The “benefits” REST is supposed to introduce
Is one of the more egregious straw men I’ve seen in a while. Never once in my twenty years have I heard anyone say it’s nice because “you don’t have to read the docs” or “it works in a browser”
REST helps with lots from thinking through clean data models to helping ensure consistency within an API.
The uniform interface, which is the defining characteristic of REST according to Roy Fielding[1], is what allows clients to interact with REST-ful APIs without "reading documentation":
> "A REST API should be entered with no prior knowledge beyond the initial URI (bookmark) and set of standardized media types that are appropriate for the intended audience (i.e., expected to be understood by any client that might use the API). From that point on, all application state transitions must be driven by client selection of server-provided choices that are present in the received representations or implied by the user’s manipulation of those representations. The transitions may be determined (or limited by) the client’s knowledge of media types and resource communication mechanisms, both of which may be improved on-the-fly (e.g., code-on-demand). [Failure here implies that out-of-band information is driving interaction instead of hypertext.]"
> About being able to use a RESTful API directly from a browser: browsers only use the GET and POST HTTP methods, among the ones REST associates meaning to. You cannot use a RESTful API from the browser, because the PUT, PATCH, and DELETE methods are not used by the browser, and even the POST method cannot be used without a UI. Also, even if you could, APIs are for machines, not humans. The user experience would be dreadful if you had to use a RESTful API directly from a browser, without a specific UI for it.
It's kinda true if you're thinking purely about submitting requests through `<form>` elements - but even then you can bend browsers to support other verbs.
There are multiple RFCs with lists of HTTP verbs and axios only supports the more limited set, which for my particular legacy project meant I couldn't use any of the creative ones.
That really just sounds like an axios issue - I've built in support for custom verbs with nothing but plain-old ES5 javascript. The verb is nothing more than a string component of the HTTP request and it's pretty trivial to modify it to your liking (even outside of standard verbs!).
Sure you can. You just can't specify it as a form action.
There's nothing to stop you from using form elements to get your user inputs as normal, then wiring up your own "submit" button that calls your own Javascript to use any method you want. JS fetch, for example, lets you put anything you want in the method: field. I think you can even specify custom methods that only have meaning for your specific server-side code.
They mean you can’t interact with a raw API from the browser. Unless you are just doing vanilla gets.
You might use a REST client instead or what some services do is offer a playground coded in JS to play with the end point but such a playground needs to deal with CORS.
But this also isn't true—fetch can run all the HTTP request verbs.
What they seem to mean is that you can't access most of them via the GUI directly, which is true, but their "solution" is to make queries and commands both use POST, which means they've now thrown away the ability to even access read-only endpoints via the GUI.
Ok I accept that - I guess you could F12, drop into the console and then fetch. Which is broadly similar to using curl.
I also don't like the idea of making queries and commands use POST. It kind of sucks (unless you are doing it for a website that is trying to avoid using JS for some reason).
In addition, I’d just like to interject for a moment. What you’re referring to as REST, is in fact, JSON/RPC, or as I’ve recently taken to calling it, REST-less. JSON is not a hypermedia unto itself, but rather a plain data format made useful by out of band information as defined by swagger documentation or similar.
Many computer users work with a canonical version of REST every day, without realizing it. Through a peculiar turn of events, the version of REST which is widely used today is often called “The Web”, and many of its users are not aware that it is basically the REST-ful architecture, defined by Roy Fielding.
There really is a REST, and these people are using it, but it is just a part of The Web they use. REST is the network architecture: hypermedia encodes the state of resources for hypermedia clients. JSON is an essential part of Single Page Applications, but useless by itself; it can only function in the context of a complete API specification. JSON is normally used in combination with SPA libraries: the whole system is basically RPC with JSON added, or JSON/RPC. All these so-called “REST-ful” APIs are really JSON/RPC.
These so-called "REST-ful" APIs are often ad hoc RPC, underspecified in many respects, and make a twisted mess of HTTP semantics and request/response payloads; and when an edge case or shortcoming is discovered, the API sprouts more wild hairs.
What's even more "fun" is when you look through the project's history and discover that at some point, someone/s on the team fixed the problem by introducing proper JSON-RPC (per spec). Maybe they even got pretty far along with cleaning up most/all of the API. But then they moved on or got pushed aside. Other devs didn't like the JSON-RPC system ("too verbose", "too much effort to refactor/whatever") and started sprouting new "REST-ful" endpoints so they can, you know, get things done faster!
This is fair, what I think ended up happening in the 2010s is that REST was pushed similar to XML, a solution for all application
problems (it wasn’t like Fielding was not pushing this view either), and realistically the useful scope of REST and Hypermedia is quite limited.
One point this article objects to is the (ab?)use of HTTP status codes for indicating object status (i.e. 404 Your student doesn't exist). HTTP status codes are an extremely loose and flexible standard and I think that semantically most of the "RESTful" uses here are perfectly in line with expectations. Shift your thinking for a moment to consider that instead of `/user/12` linking to a script it's actually just a flat file containing some information about the user - the 404 response when the user doesn't exist is perfectly reasonable. The requestor shouldn't really care whether the request was fulfilled by apache checking file existence or some PHP script. In fact, webservers are just another kind of dynamic responder and it's important not to mystify their internal workings.
The article is good, and I get the point. However, from experience, this:
> POST on /queries/enlisted-students-on-joining-date/version/1 { "date": "2023-09-22" } to retrieve all students that joined on a given date.
always ends up in a complete and absolute mess, where every possible query gets it's own random name, different parameters, ending up with duplicates all over the place. Also, while it can be possible to cache these POST requests (responses), it's additional work and more friction compared to REST. I'm not convinced the tradeoff is worth it in this particular case.
All in all, I don't know if the current proposal can be an alternative either but the idea of a "REST-lite" goes in the right direction and it's a great start.
However this is a complete no for me:
> When a requested student is nonexistent, your API can return 200 OK HTTP status code with a { "user": null, "message": "No user exists with the specified ID" } response body.
I still prefer 200 with an empty list for the 'no results' case. The same client code works, rather than having to code for 204.
- If I query for an identifier which doesn't exist - Server replies 404, this should fall into my error handling as there's a data inconsistency
- If I query say like ?category=automotive&price=1 and I get a 200 containing a json body in which there's an empty list of matches, then my client code can handle 0 matches as well as 1 or 10 with no special handling.
this is the standard we have company-wide and it works pretty well, unless you make a mistake in the uri (like picking up the wrong api version). however, all apis will return some info on their root, so it's easy to distinguish and troubleshoot
404 means that there is no resource at the requested URI. But in the case you're discussing, there is a resource at the requested URI; it's just a resource that says there's no user there, instead of a resource that gives data for a user.
In other words, using 404 the way you describe conflates two different things: an invalid user URI (maybe the range of possible user IDs is restricted, or you typed in the URI wrong) and a valid user URI that just doesn't have a user there at the time you made your request. But you probably don't want those two things to be conflated; you want them to be distinguished. That's the article's point.
Now I'm not a web dev, I've played around way back in the CGI era and since then moved on to other things. But my impression was that part of the point of REST was making it fit in a bit better in how the protocol is supposed to behave.
Early on there was a lot of stuff that did nothing but GET for everything. Done that way any layer in the middle can't make any sensible caching decisions, and even the logs are annoying to look at when you can't quickly tell apart what's retrieving data and what's changing it.
So my impression was that part of the point of REST is to shoehorn an API into something that makes sense in the way HTTP is supposed to work, and you're less likely to have something go dramatically go wrong because some cheap ISP is running a transparent proxy and deciding that the GET that's supposed to save something doesn't actually need to happen, because look, this user already made a GET to the same URL 5 seconds ago.
Now of course today everything uses SSL, but early on it wasn't that weird to run into some awful ISP that would do some sort of aggressive caching to save on upstream bandwidth, if not to actually try to recompress stuff in flight.
I agree with some of this - I've never found value in the different PUT/PATCH/etc verbs, and I think the lack of examples of good "pure" REST APIs indicates that pure REST doesn't work easily for most projects.
I do think this throws away some pieces that are really valuable though:
1. URLs for concepts are a good idea
2. Distinguishing between read-only operations (which can be cached) and write operations using GET and POST verbs is useful
Personally I've found myself settling on "REST-ish" JSON APIs:
- Every domain concept has a URL, and a standard consistent JSON representation that's also returned by list endpoints
- GET for read operations, POST for anything that performs a write
- I don't like content negotiation via the Accept header, so instead if I need to support alternative representations I'll do that using file extensions, .json vs .csv for example
Yep, this is pretty much what I've settled on myself.
GET for read, POST for write. And a url made of a slash-terminated "resource" followed by an "action" verb. user 123 + edit = "/user/123/edit" ; user 123 + default(show) = "/user/123/" ; list of users = "/user/list" ; etc.
When applied to web pages, it means you can have <form action="edit"> and the "action" attribute of the form directly matches the "action" of the backend/router/API. I like that, it makes me feel warm and fuzzy. :-)
I don't like how undiscoverable it is, and how it blocks browser-based debugging. I'd much rather be able to open an issue with a GET link to a stable representation, where possible.
Cloudflare doesn't support it for caching (more specific Cloudflare doesn't obey the Vary: cache header).
Implicating isn't a great way to document an API. You could easily do something you didn't intend to do, which could be no big deal or extremely costly.
Exactly. If you ditch the bikeshedding and no true scot arguments, REST is just a loosely defined naming convention around HTTP verbs, that almost every company I have been apart of agrees on / disagrees on certain minor aspects. That being said, I am thankful that we moved to using some form of "REST" (however you define it) as it was infinitely better than WSDL/SOAP. gRPC is nice in some areas, but these days I find myself doing what the article says more often than not -- just creating business-definition specific resources that utilize GET/POST and JSON.
yawaramin, my former reasonML brother, you won't remember me, but I am so happy to have you comment on my comment even if it is tongue in cheek! Hope all is well.
The three common types of "REST" I have encountered in enterprise dev are:
1. Ancient XML/SOAP based APIs Frankenstiened to use JSON
2. Poorly implemented RPCs advertised as "JSON REST API", usually the entire thing either relies on POST requests but sometimes GET is used to make stateful changes.
3. Things that should obviously be RPC split into dozens of anemic JSON endpoints that the caller has to re-construct to get anything useful
Incidently, my company's leadership got inspired by Amazon's API culture and made # of APIs a metric that teams must hit. The result is every single endpoint being deployed as a separate API, and teams blocking access to native vendor APIs so each operation can be republished as a new API. :)
Yea. I assume from the article it's an alias for an HTTP interface that usually uses JSON payloads. I use those in a few projects, both work and personal. Are they Rest? Maybe. They use Django REST framework. But I think of them and describe them as HTTP APIs.
Well, if we're being truthful, none of us use REST. We're all just cherry-picking the parts of it that work for our organizations... and that's absolutely fine. The programtic discoverability portion of REST always struck me as pretty poorly thought-out anyways.
I think you missed the point. You 100% use it every day. You’re using it on this website right now. Your web browser shows you what actions you’re able to take against the HN api. Neat, huh?
While I think some folks are getting caught up in the details, I thought the overall premise of this article was correct:
1. First, as other comments here are pointing out, in the industry people use "REST" to mean lots of different things. Some folks just use it to basically mean "An API over HTTP that uses JSON and HTTP methods (GET, POST, etc.)". For clarification I like to refer to that as "REST-lite". The author is talking about what I like to refer to has "true REST", which is strict about entities, verbs, etc. and the format of calls like PATCH.
2. I think the authors general points about why "true REST" is usually a bad idea have been proven out, and like most bad ideas that originated around that time period, they came about because people had a fantasy idea of how we would interact with resources on the web that wasn't born out in reality. I essentially thought his point about "being able to use any API without understanding it first" was spot on. I also think there were other good points about the supposed benefits that nobody really uses, or that aren't that much of a benefit (these days, who really cares about supporting multiple formats of an API like XML in addition to JSON?).
At the end of the day, after decades in software development, I've pretty much come to the conclusion that if your API framework doesn't look like some flavor of RPC, it's bad. This is just fundamentally how most software developers think: "I want to do some operation X, so just let me make a remote procedure call that is named X". This higher-order structure is usually a mistake that's trying to get developers to conform to some ivory tower way of thinking that doesn't work.
> 1) It’s simple, and easy to understand. You can perform the same actions on all resources, so you don’t need to learn the details of a specific API to use it. 2) The API is usable via a browser.
then the part that this article is missing is HTML. REST APIs require HTML (or equivalent), because (via the the browser) the HTML self-describes what actions can be performed on it. If you return `<a href=/profile>My Profile</a>`, then the user knows that clicking on it will take them to their profile; they're interacting with the API without knowing its details.
A lot of people say "ah to hell with it REST has no meaning anymore" but since the premise of the article is to engage with the meaning of REST, well, that's kind of on them.
See the @recursivedoubts essays in another comment on this post too, they're much more in-depth than my comment.
I thought a practical benefit of REST (probably the only one I really care about) is that it makes APIs more scalable by allowing caching proxies to safely cache responses to GET requests? At least I thought that was a big part of the original pitch, but I don't see it mentioned here.
Those 2 were never the claimed benefits, and the fundamental "problem" is not a problem: a LOT of data processing is making changes to records. And you can encapsulate a lot of logic behind making changes to records.
And this makes most software easier to understand:
"Show me your flowcharts and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won't usually need your flowcharts; they'll be obvious." -- Fred Brooks
I was pretty annoyed expecting some awful conclusion that we had to do something entirely different, resting upon what felt like extremely shaky grounds. A one-auarter decent OpenAPI will elaborate what endpoints do, which felt like all that was missing.
In the end though, the author isn't really arguing against what we have, expect they want /commands and /queries prefixes.
Given the way some endpoints can be overloaded with disparate behaviors, I don't disagree that sometimes it can be confusing for all the verbal to apply to an endpoint. But POST /students/enroll seems more than fine to me, and trying to wrestle out a new norm, where URLs aren't broken down by resource type but are broken down instead by behavior-class seems absurd & a folly.
> The fundamental issue with REST is that it defines operations that manipulate data structures. It’s about structure and structural changes, not behaviour. It’s imperative, rather than declarative.
If it's about structure, wouldn't that be declarative? Aren't behaviors typically imperative (move this here, drive that there)?
The author has almost perfectly described the pattern behind GraphQL mutations!
That is, a command on the server that represents a user action, takes input, and returns the updated data that has changed. While many people dislike the whole GraphQL stack (I love it, it solves all the problems the author has with REST), I think we can all agree with the usefullness of the pattern that mutations are built upon.
If you’re designing something right now, I want to remind you of one piece of shared but uncommon wisdom:
So many of your users will not make it much past the elevator pitch for your idea. They will feel willfully or even recklessly entitled to the notion that if your tool allows something, the.n it was meant to be used that way.
I don’t have any quick fixes for that. I doubt they exist. There are some very clever things I have seen in the REST domain that are lumped under advanced topics that make a lot of sense but I don’t think I have ever seen in the wild. So if an idea feels important, you have to aim it at the journeyman if you expect it to be used at all, and beginner if you can figure out how without losing them.
There are some things that simply don't fit into the REST structure, the most common one for me is bulk operations. Operating on multiple instances of a specific resource is a common thing, and you need a non-REST endpoint to do it properly. Of course you could just iterate over the items, but then you can never have transactional behaviour (either no write happens or all of them happen). And of course it's terribly slow for larger number of items.
Another annoyance is GET endpoints with complex filters like e.g. the index route for a specific resource. It's really annoying to be limited to the query params instead of a request body for that.
People will still be arguing about this in ten or twenty years when many new services use AI agents on a blockchain that automatically pick up DSLs invented a few days before by other AI agents and use them to negotiate and execute service orders on your behalf.
Eventually you may get something like a high dimensional protocol used for converting and syncing neural adapters on the fly, enabling "multi-brains" to collaborate opportunistically. But even then there will _still_ be people having this exact same argument.
Anyway, I feel like a lot of people actually moved on to GraphQL years ago. I think I probably need to catch up to that trend still.
I went into this article strongly disagreeing but I think the alternative presented at the end are valid ways to structure an API and for those who very loosely follow REST structuring would probably consider the alternatives "REST" too.
REST seems to have different definitions depending who you talk to. Some define it very strictly (as the opening of the article seems to do) and others define it very loosely (as a JSON interface with GET/POST/etc commands). For those who implement REST loosely, the suggested alternatives fit right in.
> So when the server receives a PATCH /schools/{school-ID}/students/234 { "lastname": "Luthor" } request, it needs to understand that a student has requested for their lastname to be changed, and it then needs to decide what to do about it. What if some fields cannot be changed? What if the value of some fields is constrained by the value of some other fields? By allowing an arbitrary PATCH operation, these concepts are hard to model and validate requests against.
If there are validation issues with user-provided data in a PATCH, then the API should tell the user what they did wrong and reject the update, plain and simple. How would their solution react to bad data? How would it be different from what any implementation of PATCH does?
> POST on /queries/enlisted-students-on-joining-date/version/1 { "date": "2023-09-22" } to retrieve all students that joined on a given date.
So instead of a browser-accessible GET with idempotency standards, you need a POST and you need to serialize JSON just for a single field?
> POST on /commands/report-student-lastname-change/version/1 { "student-id": "123", "new-lastname": "Kent" } to report a lastname change for a student.
Great naming: I initially thought this would be about a report about last name changes, not a way to add new data.
Should there be separate endpoints for each field of each entity? That might replace “validation” of no changes to fields that cannot be changed, but it does not replace validation of last-name-is-not-empty or national-id-number-is-valid.
What if the changes are provided in a form that allows to edit all data? Do I need to send separate commands for each user-edited field? If using the bulk form (POST /commands), what if one of the commands fails — will the previous commands be rolled back, or will the DB end up with partially-edited data?
> When a requested student is nonexistent, your API can return 200 OK HTTP status code with a { "user": null, "message": "No user exists with the specified ID" } response body.
And now you can’t even automatically detect error conditions and have consistent handling for them, at least not with a schema like this.
> I even used HATEOAS principles when building HTTP APIs
I found your problem.
> About being able to use any API without understanding it first
That's a HATEOAS thing, as I understand it. REST just means that you have resources and verbs, and the verbs have ~relatively~ consistent meaning. If I see some GETs and PUTs and PATCHes and DELETEs I can get a pretty good idea of what's going on. It doesn't mean I understand all the details of your application logic.
It drives me crazy that every RESTful API I've ever seen in the wild has been running under a full-blown Tomcat or nginx (or some equivalent) web server when only a tiny, tiny subset of the HTTP protocol is actually needed for the API being supported.
there's a reason why REST is still the most widely used and the RPC type examples have faded.
Here's the thing, domain specific language specification both in source code and at various API interaction points are a good thing. But they are a thing that takes time to figure out, and they have a tendency to change. REST is still the best starting point and I challenge anyone to say that RPC is "better". We've tried it before it wasn't better. Oh you mean we did it wrong? Well now, that's the real trick isn't it?
The trading example is bad, since we do talk about posting limit orders even outside of a REST context -- though it refers to it staying in the book and not matching immediately.
REST is a design pattern. Do you use the bridge pattern for all your Java code? Nah! Do you use an ORM for all your data access and never drop down to SQL? Nah.
I'll have to add a client API to one of my projects soon and I've been thinking about this particular problem lately.
The one API I worked with a lot is that of VKontakte. It's as unRESTful as they come. You send requests to URLs like api.vk.com/method/users.get. The HTTP method doesn't matter at all, how you pass parameters doesn't matter at all (can be a combination of query and form-data for all it cares), the version is just `v` parameter, the access token is also just a parameter, and errors come with 200 OK. That is kinda not well thought out. But only kinda.
The one I'm contemplating is somewhere in between the two. The endpoints are still "methods", but the HTTP method does have a meaning: GET is for retrieving something, and POST is for active actions. The access token will have to be passed in Authorization header. The version will still be a `v` parameter. There will not be any IDs in the paths because of how awkward that is. The errors will set the HTTP status code.
Versioned "commands" are an improvement I'd like to see more of (it could even be useful in existing REST-lite APIs - I've used similar concepts to this in some prior work)
And the key point around business logic being misrepresented is definitely worth a ponder.
Notable that a lot of this sounds like a reinvention of DDD (Domain Driven Design) concepts - or at least if the author is applying DDD they make no mention of it. Which I wholeheartedly support..
Seems like two problems being conflated though:
1. Low level interactions - generally between systems. Sometimes I _want_ my API not to hold any high level business logic. It'll do basic verification of individual entity events (changes) to avoid epically stupid mistakes, but otherwise is a dumb resource used by other, more high level controllers.
Often this scenario is basically a thin wrapper over storage mechanisms, or a single abstraction atop a collection of other APIs
In this scenario, REST or other "non-business-aligned" options are perfectly fine - if they were any higher level then I'd be constraining the possible actions to what's implemented in a single place.
But by keeping it granular, and aligned explicitly to direct interaction with specific models (in a bounded context) then those changes _should_ be isolated.
2. Intent based APIs which perform high level actions that are aligned to ubiquitous language.
These would be a great fit for the proposed method in the article - and I'd posit that many of us already use some form of this in our APIs, whether through naughty blurring of what "REST" is supposed to mean, using GraphQL, or other similar alternatives.
Generally these are user facing, or at least are at the boundary.
Personally I'd maintain these as a separate thing (or at a separate top level route) which leverages the lower level interfaces to perform more complex, potentially multi-modal activities.
---
*TLDR* what the article proposes seems useful, but (IMO) more as a good reminder to be clear about your APIs' purposes: are they an interface to a bounded context, a model, or an aggregate?
What the author seems to miss is that a "resource" in the REST sense (in practical usage at least. I'll leave the official recommendation pedantry to those who care for it more strongly than I do) doesn't need to reflect a data structure.
It's definitely easier to link data structures to REST commands--I imagine REST was created with them in mind--but the recommended "commands" can still be thought of as resources. Not every REST verb applies to them, true, but that's not really impactful or relevant. The point is that you can continue structuring your API in typical REST fashion, but with the addition of command-oriented RESTful paths that implement the appropriate verbs for that command.
The author's own suggestion, `POST on /commands/{command-type}/version/{command-version}`, is still effectively REST as far as I'm concerned, with the identifier being a string ('command-type') instead of a number or UUID. The structure of the API hasn't changed, we just broadened our definition of a resource. Purists might disagree there, but from a day-to-day practicality perspective, nothing significant changed in our API development. The article is arguing semantics rather than actually criticizing REST as it's used in practice.
That said, the author does seem to have listed a few things I disagree with:
> So when the server receives a PATCH /schools/{school-ID}/students/234 { "lastname": "Luthor" } request, it needs to understand that a student has requested for their lastname to be changed, and it then needs to decide what to do about it. What if some fields cannot be changed? What if the value of some fields is constrained by the value of some other fields? By allowing an arbitrary PATCH operation, these concepts are hard to model and validate requests against.
I'm not sure what difficulty they're seeing here. If some of the values can't be changed, it's a failed request, return the errors without any updates. The "arbitrary" parts feel like they derive more from their own API implementations than anything practically REST-ful. Or maybe it's in the official REST spec, which, as comments here demonstrate on repeat, holds very little ground.
> This also sucks, because HTTP status codes are about the HTTP protocol, not your business semantics. The 404 Not Found status code indicates that a path doesn’t exist. If you use it to say that a student doesn’t exist, these two get mixed up. If a client invokes the wrong path by mistake, there’s no telling whether it’s due to a protocol error or to a nonexistent student.
If there's no student with the ID 4, then the path `/students/4` doesn't exist. It's not saying that `/students/<id>` doesn't exist, because the path-with-variable only exists as a theoretical construct, not an actual path. So I'm not quite seeing their argument here (unless, again, they're arguing against the semantics of the official recommendation, which I've never seen implemented in a straight fashion anywhere). And if there's user error in invoking the wrong path, then that's hardly on the API design.
Just to clarify, my incredibly vague point is that people having been saying FreeBSD and now REST are or should be dead for so long but they never die :)
> The “benefits” REST is supposed to introduce
Is one of the more egregious straw men I’ve seen in a while. Never once in my twenty years have I heard anyone say it’s nice because “you don’t have to read the docs” or “it works in a browser”
REST helps with lots from thinking through clean data models to helping ensure consistency within an API.