
Rules for REST API URI Design - RestCase
http://blog.restcase.com/7-rules-for-rest-api-uri-design/
======
ryanbrunner
I 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
Yes, 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
Spot 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-5d576ffccb...](https://medium.com/@rdsubhas/pitiful-restful-
urls-5d576ffccb98))

~~~
rimliu

      > RESTful URLs are not to be treated like SEO URLs
    

But it does not hurt to have them human-readable.

~~~
rdsubhas
It does hurt. Examples in the article linked above.

------
tchow
When 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?

~~~
mkhattab
I believe '/courses' and 'student/1234/courses' mean two different things. The
former implies a list of courses and the later implies an association between
a student and a course.

POSTing to '/courses' creates a _new_ course. However POSTing to a
'student/1234/courses' creates a relationship/association between a student
and a course.

So, it's two different things IMO and not a duplication of logic.

~~~
3pt14159
This isn't true. Or at least not _necessarily_ true. If you're referring to
the JSON API spec you're conflating related and relationship links. The former
is strictly for GETs (GET students/5/courses) and the later is for controlling
the relationship itself (PATCH students/5/relationship/courses).

See here for more information:

[http://jsonapi.org/format/upcoming/#crud-updating-to-many-
re...](http://jsonapi.org/format/upcoming/#crud-updating-to-many-
relationships)

------
Frizi
Or, 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
I 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

------
hamandcheese
Overall 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.

~~~
icebraining
I 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
Could 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.?

~~~
pikzen
/api/sendmail, data passed in POST as JSON. POSTing doesn't have to be
uniquely identified.

~~~
icebraining
Yes, but that's RPC thinking. What if the queue is backlogged? What happens if
the email doesn't get delivered? If your client asks for the email to be sent,
it should deal with it during its lifecycle.

In my opinion, one shouldn't have a /sendmail API at all, the server should
deal with that, but if one really needs the client to do that, then /sendmail
should at least return a URL that represent the email being sent, so that the
client can keep track of it even after the connection is lost or the device is
rebooted.

~~~
pikzen
We've already established REST has crippling limitations. Introducing RPC in
it makes up for those.

You can also make it a long lived connection, that waits for a proper
confirmation, which means you might be waiting ten seconds. Or you can make it
a fire-and-forget operation, and have a resource to check if it was properly
sent. Which is back to polling. Or you can use server-sent events to have the
server notify you back when it's done.

But yes, unless you explicitly need to be able to send emails from clientside,
I wouldn't expose a sendmail resource, and would leave that to the server. (I
say as I recently implemented a resource that allows me to post an event from
client side to allow the server to send it back. :| )

Ultimately, do what works. The pure REST cargo cult is dangerous. As long as
what you do is clean, maintainable and ideally idempotent, you're good.

~~~
icebraining
_We 've already established REST has crippling limitations._

No, we haven't. I explicitly disagreed with that assertion. REST is not
adequate for everything, but it doesn't have "crippling limitations". It's
pretty good for its intended use.

 _You can also make it a long lived connection, that waits for a proper
confirmation, which means you might be waiting ten seconds._

You can't rely on the connection lasting that long. REST's design was a major
sucess on the Internet in part because it naturally dealt with the
connectivity limitations.

 _Or you can make it a fire-and-forget operation, and have a resource to check
if it was properly sent._

Yes. That's what REST means. That's my point!

 _Or you can use server-sent events to have the server notify you back when it
's done._

You still have to create an unique ID for the operation, so that the client
can tell _what_ is done. Having an URL as that ID is barely any effort.

 _Ultimately, do what works. The pure REST cargo cult is dangerous. As long as
what you do is clean, maintainable and ideally idempotent, you 're good._

There's nothing clean and maintainable about having ad-hoc mechanisms that
break the overall functioning of the API. If you're using a paradigm, be that
REST, RPC, or anything else, you should have a major reason to break it.

~~~
pikzen
... Then make an endpoint where you can get a task's status through its ID
(your post to sendmail would then return a task ID), and poll repeatedly until
it's marked as done, wasting bandwidth, your architecture, your choices
¯\\_(ツ)_/¯

Or you can let the server notify you when it's done, and carry on with your
work. As a bonus, most clients able to receive SSE already include automatic
reconnection to the feed if it ever gets cut.

~~~
icebraining
They're not incompatible, though. I'd make everything as a REST endpoint
(since you need to create a resource with an ID anyway for SSE, otherwise, how
will the client know _which_ email want sent?), and then add SSE as an
optional performance improvement layer on top of those endpoints that may
benefit from it.

This is how WebSub (formely PubSubHubbub) works on top of RSS/Atom, for
example.

------
teddyh
Nice 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
I actually think the singular/plural question isn't too important - the key
thing is that it's consistent across the whole API.

------
forgottenacc57
I 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
I now have a lot to deal with API which was created by people sharing your
view. It is a nightmare.

------
tudorconstantin
I 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/stud...](http://api.college.com/students/3248234/courses/physics/students)
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?

~~~
baddox
Ruby 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)

------
vbezhenar
Two 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
Regarding your point in 2: do you store your docs in a directory called
Documents or Document?

~~~
ErikBjare
I 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).

------
TrickyRick
I 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

~~~
stingraycharles
A 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
The 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
In 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.

~~~
PeterisP
In practical applications, I'd want it to be the opposite way around - the
server will be coded to support multiple formats since it serves many clients
and many types of clients, but any particular client explicitly targeting that
server will likely implement just a single format for exchanging data with
your app.

So you'd want the server to send _all_ the formats it can provide (i.e. as a
list of different resource URLs) and the client chooses whatever it prefers,
entirely the other way around as you describe.

Furthermore, server being able to dynamically respond "I don't do your
preferred format" is not a desirable outcome; from the client perspective
you'd want to know if the format is available _right away_ , and for a whole
class of URLs, not an individual URL at the very last moment. I.e., I'd want
it to be equivalent to a "compile time check", that an URL scheme promises
that the server will always be able to return type X for such URLs, instead of
a "runtime check" where the availability of a particular format is known only
after you try it.

~~~
stingraycharles
> So you'd want the server to send all the formats it can provide (i.e. as a
> list of different resource URLs) and the client chooses whatever it prefers,
> entirely the other way around as you describe.

What is the advantage of that, compared to having normal Accept: encoding ?
Please note that in the negotiation phase, a server uses the format that the
client actually _prefers_ ; it's able to tell the server these things (e.g.
"if you have json, give me that, otherwise I'll take XML).

Your solution requires an additional round-trip for this same negotiation.

------
alexchamberlain
I 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.

------
elvinyung
Orthogonal: 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).

~~~
lioeters
I 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..?

------
zaidf
On 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
Wouldn'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
He'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.

------
partycoder
URI 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
I 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.

~~~
concede_pluto
All you need to know about WHATWG is that the monstrosity they're billing as
"HTML" has neither version numbers nor a formal grammar.

------
mxstbr
This 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
GraphQL 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
Sure, 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!

------
jasonkostempski
RFC 3986.

------
masudiiuc
is this rules are perfect?

