12 ms·
Rules for REST API URI Design
- masudiiuc 9y agois this rules are perfect?
- TrickyRick 9y agoI would disagree on the artificial file endings. It's a very transparent way to allow the client to request a resource with a specific content type, visible right there in the URI. After all the .json and .xml are unique representations of the same resource and can reasonably have their own URI
- eponeponepon 9y agoYeah, there aren't many situations where that really washes - it's a reasonable rule to have in mind when designing your API, but it seems to clash with the principle of designing "for your clients, not your data". Your clients are often using a browser and don't get to pick the content-type they request. I guess you could contrive to have /resources/xml/apples and resources/json/apples serve the same content, but otherwise it's query strings or interposing a 'choose a representation for your content' page. Naturally, none of this is a problem if your clients are not humans, I suppose.
- hdhzy 9y agoThe thing is that resource should have ideally one URI and selecting representation should be done through content negotiation (e.g. Accept header). Of course having an option to use file extensions makes debugging easier in a browser.
- jmull 9y agoI don't mean to pick on you at all, and I hope this doesn't come off that way... but this is a very interesting statement. You start off with a statement of ideals which you then directly contradict with a real, practical concern. I think that has to mean the ideal isn't a good one and in fact -- here's the interesting part, IMO -- one or more of the considerations on which the ideal was based are also invalid. Personally, I think the "lesson" here is that we should not aspire to have a one-to-one relationship between URI and resource. The attraction is that it's simple. But I think it's clearly too simple; that is, it is too inflexible to be useful: resources cannot be transferred between hosts or shared by multiple hosts; resources can only be organized in a single fixed way (for all time!) within a service (itself with a fixed organization within its host). It seems better not to try to impose these restrictions on resource identity and instead separate how a resource is identified from how it is retrieved. E.g. use a UUID to unambiguously identify a resource across all space and time, but retrieve the resource using a set of URLs which can change over time. I mean, the idea that a resource can move (its URL has changed) has been is built into HTTP for decades. Why attempt to create a service that assumes this does not happen?
- stingraycharles 9y agoA URI should represent a resource, regardless of its serialisation format. Serialisation really is part of the HTTP headers, and the support in there is great. Using this allows the web browser and the web server to completely negotiate an acceptable format themselves. Adding the serialisation format to the URI also creates a conflict: what would happen if you request a .json, but the web browser doesn't accept this format ?
- eponeponepon 9y agoThe last case sounds like user error to me. You could say the same for using Accept headers - what happens if the user tells their line-drawing client to request a spreadsheet?
- stingraycharles 9y agoIn the case of accept headers, you would have actual negotiation; the client sends all of the formats it accepts, and the server chooses the most suitable one. If none are available, it would return a 406 Not Acceptable. The problem with putting another, incompatible serialisation format on top of that is that it creates conflicts, and inherently requires one to reinvent the wheel, or have a less flexible solution. I simply fail to see what problem it solves.
- eponeponepon 9y agoThat's a fair response, but you're presupposing a client that behaves as it's supposed to. To my mind, the problem it solves is familiarity - average users are accustomed to "file.html", not "file(oh hey, send the text/html version please)". All of this is heavily influenced by who your users actually are, of course.
- rimliu 9y ago> the client sends all of the formats it accepts And currently many clients only support JSON. As do many backend systems.
- 9y ago
- kuon 9y agoI agree. Also it might be more convenient when doing get request. Imagine an API generating images like /cat/300x300.jpg the ability to switch to .png is really handy. Of course you could do /cat/300x300/png and don't use extensions. I think it's up to particular use cases.
- alexchamberlain 9y agoI think these are rather strongly written, but certainly a reasonable list. Servers should be carefully implemented to be forgiving of bad input, but always ensure perfect output.
- tudorconstantin 9y agoI love the REST principles. The RESTful ideas framework (I call it like this because I lack a better name for them) helped me organize my applications in a much more consistent manner. I wonder if a URI like http://api.college.com/students/3248234/courses/physics/students http://api.college.com/students/3248234/courses/physics/stud... would actually make sense to get a list of all the students who take the same physics course that student 3248234 takes. If yes, is there a web framework where such generic routes can be defined?
- andreareina 9y agoIf you're using the url as a hierarchical representation, it wouldn't make sense. What happens if you ask for a circular reference like /students/3248234/courses/physics/students/3248234? You can either query the student to get the course's uri, then access /courses/<course_id>/students.
- baddox 9y agoRuby on Rails easily supports arbitrarily nested resourceful routes, but they strongly (and wisely, in my experience) advise against precisely this type of deep nesting. http://guides.rubyonrails.org/routing.html#nested-resources http://guides.rubyonrails.org/routing.html#nested-resources http://weblog.jamisbuck.org/2007/2/5/nesting-resources http://weblog.jamisbuck.org/2007/2/5/nesting-resources
- teddyh 9y agoNice rules, except for #7. Class names are singular, as are most SQL tables in modern SQL. I think this pattern should be followed also in URL design, since it’s more common to refer to a single at /person/327 than the list of all people at /person. However, everyone designing an API should be aware that the REST principles really don’t work very well without HATEOAS, and HATEOAS does not require any “designed” URLs, just that URLs be persistent. Any client to a real REST (HATEOAS) API requires exactly one URL, the root. All other URLs should be discovered by the clients by links in the resources given by the API, starting with the resource present at the root URL.
- eponeponepon 9y agoI actually think the singular/plural question isn't too important - the key thing is that it's consistent across the whole API.
- PeterisP 9y agoI think that discoverability is overrated, because you can discover URLs (as in, what options are available) but you can't discover what exactly those URLs will do (especially for non-GET options) or find URLs that will do exactly what you need, so any discovered URLs aren't usable anyway unless you already knew beforehand what exactly you are trying to discover. The client (if it's not a human) can't magically discover the semantics, and a HATEOAS API can't properly describe the semantics - it will give you a relationship type string that might be descriptive of what the URL will do, and that's it. In any case, you need to define a mapping between "I want to do X" and an item on the server side; and when writing a client there's not much practical difference (only a conceptual one) between linking "do X" to an URL string versus linking "do X" to a HATEOAS relationship string. You gain some stability if the service renames some methods, but unless you're really sure that it was just a cosmetic change and none of the semantics has changed, you need to re-verify everything anyway if it happens.
- hamandcheese 9y agoOverall this seems like a clickbaity list of practices that have been well established for a while now. REST is easy when you're just doing CRUD. It's once you have actions besides "update" that things start to get a bit more interesting.
- idkfa 9y agowhen things start to get interesting, it's better to switch to JSON-RPC
- icebraining 9y agoI disagree, it's fairly simple, people only find it hard because they're thinking in RPC terms - in commands instead of resources. An action is simply a new resource you create. You don't send_emails(), you create a new email sending resource, which has its own URL you can check in later (giving you built-in resilience to network cuts and other problems). I'm yet to find an action you can't easily model with resources and their representations.
- matrixagent 9y agoCould you maybe elaborate a bit more with your send email example? We've been struggling a bit with API design and exactly this kind of thinking everything as a resource. I'm still having a hard time to imagine exactly how "a new email sending resource" would/should look like. having something like /api/sendmail/confirmation would trigger my confirmation mail sending method internally, which clearly is RPC thinking. How would that look like with REST? How would the REST version deal with /api/sendmail/{confirmation,thanks,resetpw} etc.?
- icebraining 9y agoWhat exactly are you trying to model? What type of emails are those? Generally, trying to treat the server as a dumb API doesn't work well. If your client is the one who knows that something was confirmed, it should tell the server that, and let it worry about sending whatever emails it wants. So you'd just PUT your resource with state=confirmed, and let the server take care of any side effects that might trigger. On the other hand, if it's something like a mass email created by the user, then the client should POST to create a new "mass mailing resource", then it'd PUT all the changes made by the client, and finally PUT its state to "ready to send" so that the server can do so.
- zaidf 9y agoOn a related topic, I hear the best practice is to make use of HTTP methods like PUT and DELETE. Am I in a tiny minority that wishes we could use verbs in URIs, ie http://api.blah.com/student/32/delete http://api.blah.com/student/32/delete instead of relying on the DELETE http method to communicate that point?
- noisy_boy 9y agoWouldn't that place "delete" at the same level as, e.g., "courses" i.e. http://api.blah.com/student/32/courses http://api.blah.com/student/32/courses?
- jack9 9y agoHe's trying to articulate using arbitrary semantic uri schemes (that was just a simple example) rather than HTTP verbs. Ultimately, you have to document them in your APIs anyway and the caching reasoning (along with all the others I've heard), just haven't been useful in practice.
- tomstuart 9y agoYes, because request methods provide a uniform way of communicating meaningful information to HTTP infrastructure like caches. If a request uses the DELETE method then a cache in the middle can know something about the meaning of that request (e.g. it's idempotent) and change its behaviour accordingly. If that information lives only in the URI then there's no way for a cache to understand its significance.
- partycoder 9y agoURI normalization takes care of trailing slashes (RFC3986). If you are parsing URLs yourself try to stick to the WHATWG URL standard (https://url.spec.whatwg.org/ https://url.spec.whatwg.org/).
- eponeponepon 9y agoI hadn't seen this before - it's horrifying. Right up the top it declares that its intent is to _obsolete_ the existing RFCs - and then just breezily drops this in: "As the editors learn more about the subject matter the goals might increase in scope somewhat." Honestly, I'm gobsmacked. I really hope nobody's taking this document seriously.
- kuschku 9y agoWelcome to the WHATWG standards. The only ones who have any say in WHATWG are the large browsers, and most of the power is with Google. Their concept is also not to standardize new stuff, but to always only describe what the largest browsers (often simply Chrome) do.
- partycoder 9y agoIt may be bad but unfortunately it's the closest to a serious/workable standard nowadays regarding URLs.
- concede_pluto 9y agoAll you need to know about WHATWG is that the monstrosity they're billing as "HTML" has neither version numbers nor a formal grammar.
- tchow 9y agoWhen doing something like `student/1245/courses` there will certainly be a scenario where you want a list of courses on their own as well. If planning ahead, does that warrant designing your route such as: `/courses` where you get a list of courses and `/courses?studentId=12345` Where you get courses scoped to a student... Or is it better to just recreate a separate route such that you have /courses AND student/2345/courses? Im concerned that the latter results in duplication of code and possibly more confusion (ie not clear if the API require POST to /courses or /student/12345/courses to create a course for a student ) while the former results may cause (lack of) caching problems. Suggestions?
- idkfa 9y agojust drop this REST hype altogether and use adequate protocols for communication
- pyre 9y agoAs in design a custom TCP/IP protocol for every application?
- stingraycharles 9y agoI think there are a few GraphQL enthusiasts entering this thread right now, which has its own merits, but can live perfectly next to REST.
- konschubert 9y agoIn my company we use graphql for getting, and REST for all other operations. Might be an antipattern, but has served us well. We're also using a special flavor of graphql that returns the data as flat dictionaries instead of deeply nested dictionaries. Whether you need that or not of course depends on the client design.
- kuschku 9y agoAs in using GraphQL for this purpose, I presume. Or even offering an SQL-API. Seems more ideal than overly complicated URL schemes.
- mxstbr 9y agoThis post makes me very happy to be using GraphQL. (not trying to start another flamewar) Rather than following arbitrary "best practices" you dig up from random articles and might or might not know or follow, GraphQL forces you to write your API a certain way. That is not to say GraphQL is a silver bullet, it has it's own problems, but at least I can concentrate on my application and how it needs to work rather than reading more articles about how exactly to name my URLs.
- ryanbrunner 9y agoGraphQL is just another set of "arbitrary" best practices. If you're looking for a ambiguity-free prescriptive approach to implementing a REST API, there's plenty out there (MS, Google, and tons of other companies have very prescriptive approaches to building REST APIs), and some frameworks like Rails push you strongly in one direction (for example, most of the rules of this article are something that isn't a decision you need to make in Rails)
- mxstbr 9y agoSure, but at least these arbitrary best practices are enforced at a framework level. I can't not adhere to the GraphQL way of doing things!
- elvinyung 9y agoOrthogonal: I wish there was a good RPC protocol for the web. REST really sucks when you're not doing CRUD (which, frankly, is way more often than expected).
- icebraining 9y agoThere's nothing problematic about non-CRUD in REST, it's just a matter of not thinking in RPC terms.
- lioeters 9y agoI also prefer RPC-style interfaces, and tend to use it internally - i.e., not exposed as public API, since REST is usually the expected standard. In one application, I was able to use the same group of commands for both AJAX and WebSockets, which just mapped to a folder of functions. It was a pleasure to forget the boundary between client and server, and treat the server as just an asynchronous function call away. I suppose there's nothing stopping me from using a similar structure for external parties to consume the data, it's just that there's no established standard/protocol for how to expose and document such APIs..?
- forgottenacc57 9y agoI can't help feeling a great deal of the effort that goes into API definition and maintenance is pointless and a waste of time.
- rimliu 9y agoI now have a lot to deal with API which was created by people sharing your view. It is a nightmare.
- forgottenacc57 9y agoI can't help feeling a great deal of the effort that goes into API definition is pointless and a waste of time.
- Frizi 9y agoOr, you know, just use GraphQL as your protocol and be done with this. Everything that's problematic in REST is clearly specified here and stays very easy to use. You can focus on real problems from now on.
- ruslan_talpa 9y agoI think the problem goes deeper then REST & GraphQL, it boils down all the way to SQL and not a lot of people are seeing the problem because it is a "boiling frog" problem. Let me explain. So only a decade ago, all of the work was being done on the server and everything was great (... in a way :), and that's because there you had SQL and you could ask quite complicated questions with it. Now along comes XMLHttpRequest and the iPhone and a lot of the "business logic" starts moving to the frontend slowly. It was a natural thing to do when you need just a little info from the server, basically make "GET /items/1" mean "SELECT * FROM items WHERE id=1", because if you strip all the auth stuff away, that is the essence of that URL. So REST is born. It worked for a while, while the frontend code wasn't doing a lot. But now, we are in a state where everything is being done by the fronted. Now remember all those complicated questions we had to ask of our data, they did not go away, but one thing did, SQL, and all that remained was REST. REST maps to a very limited subset of SQL power, basically all it can do (and still be RESTful) is generate queries like this "SELECT * FROM items WHERE cond1, cond2 ...". Devs were used to working mostly only with queries like this (and ignore joins) because the db was close and there was no (big) penalty for firing 100 queries like these, but now when things moved to the frontend, people started remembering they need to take network latency into account and suddenly "getting everything in one go" ... which is basically a join, became important again. This is what sparked the GraphQL popularity, getting everything in one step, aka ability to express a join everything else is not that important (standard/tooling/types is of course important but it could have been invented for REST also). Having said all that, GraphQL is still far from the expressivity of SQL and people will still wonder how can they express in GraphQL questions that are easily answered by SQL. Everybody is still thinking in terms of "defining an api" but recently a new way of thinking about this problem started to emerge. Defining a way to translate a HTTP request to a SQL query, i.e. trying to expose the power of SQL in a safe way to the frontend. There is no predefined list of endpoints/types, every requests gets transformed to a SQL query and executed. This is what PostgREST [1] is doing. I know it sounds scary and dangerous but it works :) Try the Starter Kit [2] to get a taste and if GraphQL is your thing, you could explore the same idea using subZero [3] [1] https://github.com/begriffs/postgrest https://github.com/begriffs/postgrest [2] https://github.com/subzerocloud/subzero-starter-kit https://github.com/subzerocloud/subzero-starter-kit [3] https://subzero.cloud https://subzero.cloud Edit: typos
- ryanbrunner 9y agoI don't disagree with most of the rules, but it's more than a little ironic that the supporting quote at the top of the page that the article seemingly hangs off directly contradicts most of the article: > The only thing you can use an identifier for is to refer to an object. When you are not dereferencing, you should not look at the contents of the URI string to gain other information. - Tim Berners-Lee By this quote, caring about the hierarchical nature of URIs, pluralization, and enhancements to readability are somewhere between irrelevant to actively harmful (since they promote the idea that the URI contains information beyond identification) By the logic that a URI is supposed to be used for identification and identification alone, these two URLs are identical in terms of value: http://api.example.com/louvre/leonardo-da-vinci/mona-lisa http://api.example.com/louvre/leonardo-da-vinci/mona-lisa http://api.example.com/68dd0-a9d3-11e0-9f1c-0800200c9a66 http://api.example.com/68dd0-a9d3-11e0-9f1c-0800200c9a66
- macca321 9y agoYes, The article is talking about REST as in "JSON RPC over HTTP" as opposed to REST as the person who came up with it (Roy Fielding) intended. If you are worrying about URL readability you aren't doing REST.
- rdsubhas 9y agoSpot on. RESTful URLs are not to be treated like SEO URLs. But unfortunately most people don't see the difference. One example in the article is especially harmful (/students/<id>/courses/physics for querying). The single best slideshare I've found on REST is Teach a Dog to REST. Old but gold. https://www.slideshare.net/landlessness/teach-a-dog-to-rest https://www.slideshare.net/landlessness/teach-a-dog-to-rest (And a shameful plug - https://medium.com/@rdsubhas/pitiful-restful-urls-5d576ffccb98 https://medium.com/@rdsubhas/pitiful-restful-urls-5d576ffccb...)
- rimliu 9y ago> RESTful URLs are not to be treated like SEO URLs But it does not hurt to have them human-readable.
- vbezhenar 9y agoTwo points that I don't understand. 1. Using dash instead of underscore as a space replacement. Underscore is a much more natural as a space replacement and dash is actually a punctuation character. An article gives the following reason: "Text viewer applications (browsers, editors, etc.) often underline URIs to provide a visual cue that they are clickable. Depending on the application’s font, the underscore (_) character can either get partially obscured or completely hidden by this underlining.". It's not convincing at all. I've never encountered this glitch. 2. The keep-it-simple rule applies here. Although your inner-grammatician will tell you it's wrong to describe a single instance of a resource using a plural, the pragmatic answer is to keep the URI format consistent and always use a plural. But why plural and not single? English is a weird language and it has numerous exceptions for plural form. Isn't it simpler to use single form? I'm always using single form everywhere, works fine for me.
- vmasto 9y agoRegarding your point in 2: do you store your docs in a directory called Documents or Document?
- ErikBjare 9y agoI think this point is great. When you name the resource you are naming a directory/table. You pick a file/row in it by either using an ID or query parameters. That's how you reduce the many to the one. Just like how you say "one of the students" (singular lookup, /students/42) or "students who study CS" (plural query, /students?studies=cs). Not "one of the student" (singular, looks ok: /student/42) or "student studying CS" (plural, but reads as singular: /student?studies=cs).
- ErikBjare 9y agoMore about point 1 here: https://stackoverflow.com/a/6153129 https://stackoverflow.com/a/6153129 The argument with the most weight right now is probably that everyone uses dashes and there are no significant advantages to using underscores. So, to help the web a little more consistent, just use dashes (unless you have some unusually good reason not to).
- jasonkostempski 9y agoRFC 3986.