
Use links not keys to represent relationships in APIs - sarego
https://cloudblog.withgoogle.com/products/application-development/api-design-why-you-should-use-links-not-keys-to-represent-relationships-in-apis/
======
twblalock
Here is the problem with links in a nutshell:

> The server is now free to change the format of new URLs at any time without
> affecting clients (of course, the server must continue to honor all
> previously-issued URLs).

If you have to honor all previously-issued URLs then you aren't _changing_
your format -- you are supporting two formats from now on, the old one and the
new one.

You can of course tell your users that you will deprecate the old format, but
unless you are as powerful as Google your users may prevent you from enforcing
a deadline for deprecation.

If the URLs in your API responses are FQDNs rather than relative paths, all of
this gets significantly harder to deal with.

Even if you figured all of that out, links are not idiomatic if your users
consume your api via an RPC or GraphQL.

~~~
jayd16
>(of course, the server must continue to honor all previously-issued URLs).

Why is this true for non-user facing urls? Seems like an argument could be
made that you only need to support the urls for as long as the response was
cacheable.

~~~
barrkel
It's an API. The client isn't likely to be a browser. If it's integrating with
another system, those links may have quite a long life.

~~~
randallsquared
Caching behavior is not only about browsers.

~~~
barrkel
For sure, but for most people using APIs to integrate with systems, caching is
an unwanted middle way between always fresh and stored persistent, where local
storage is the "cache", except it isn't invalidated using HTTP logic.

~~~
jayd16
>except it isn't invalidated using HTTP logic.

You should be doing this. It helps a lot, actually.

------
raquo
I never had any uncertainty regarding where I can use a given entity id in a
well designed API. Fix the naming and organization of your API if that's a
problem for your users.

Conversely, I often need to log or store API-provided entity ids on my side,
and having to parse it out of a URL or store irrelevant URL bytes in my own
database would be really annoying.

You're not going to avoid the need to compile entity URLs on the client side
either, unless you only make requests to entities returned by the API, which
would be a weird constraint to design client code around.

I really don't see the point to any of this.

~~~
underwater
I would never feel comfortable `fetch`-ing the URI provided by the API without
validation. That sounds like a security issue waiting to happen.

~~~
pmontra
Does an API returning only relative URLs address your concern?

The validation would be intrinsic in the client side concatenation of the
server address with the relative URI.

~~~
raquo
(Not the GP) Yes, when done right, but it's iffy.

For example, the article includes URLs like "/people/123". The security of
having a leading slash in these depends on API base URL.

"[https://api.hotstartup.com/v1"](https://api.hotstartup.com/v1") – safe

"[https://api.hotstartup.com"](https://api.hotstartup.com") – unsafe with
".evil.com/people/123"

I wouldn't really call this particular scenario a security issue, but allowing
such things is a bad habit that _will_ eventually bite. It's like not HTML-
encoding values like usernames that you "know" are safe (but you know are not
HTML-encoded).

~~~
nertzy
Yes, this is why developers should use URI-building libraries instead of
direct string manipulation to modify URIs.

If I visit an HTML page with a link to “.evil.com/people/123” and click on it,
the user agent won’t append “.evil.com” to the hostname. You’d instead get
something like
“[https://api.hotstartup.com/.evil.com/people/123”](https://api.hotstartup.com/.evil.com/people/123”)
which would be safe (if not broken).

------
WanderingWaves
I think this takes an overly-simplitisic view of APIs. Going by the primary
example in the article, by representing a pet's owner as a link instead of an
id, they're basically discounting the idea that there may be separate
endpoints that take in an owner id. For example, if there was an endpoint that
let you get the invoices by customer, you would still need to understand the
templates for that endpoint.

More fundamentally, I think it's trying to solve a smaller problem in the face
of a much bigger one, you still need to know what the response of any given
endpoint is going to be. Just because they've passed me a link, doesn't me I
don't need documentation on what endpoint that link points to. I still need to
know that the owner is a link to the people endpoint so I can properly parse
that result. That in turn requires just as much documentation (IMO) to
describe the relationships as it would to properly document your URI
templates.

Obviously, the primary reason to use links over ids is to give the developers
of the API more control over changing things like routes and Ids and whatnot,
but I feel like it is a bit disingenuous to make it out to be a much better
user experience or something, since it really isn't.

~~~
rich-tea
There shouldn't be separate endpoints that take an owner's ID. That's bad
design. The owner endpoint should contain a list of invoices, ie. links to the
invoice endpoint.

~~~
jeltz
Do I misunderstand you or do you suggest that the customer object should
include the list of all invoices? That does not scale. Imagine if it is
something more common like transactions where a user can have made thousands
or tens of thousands.

~~~
randallsquared
An API should usually prioritize ease of development over faithfully exposing
database or architecture internals.

However, you can have it both ways: in the customer response, use

    
    
        "invoices": "/customer/7sn2J/invoices"
    

and have the server 307 redirect to "/invoices?customer=7sn2J", or whatever
you prefer.

------
k_bx
There's literally not a single upside of this shown in the article.

> The server is now free to change the format of new URLs at any time without
> affecting clients (of course, the server must continue to honour all
> previously-issued URLs).

No more than it was previously.

> The URL passed out to the client by the server will have to include the
> primary key of the entity in a database plus some routing information, but
> because the client just echoes the URL back to the server and the client is
> never required to parse the URL, clients do not have to know the format of
> the URL.

Instead, you now have to require new kind of knowledge, one of the keys which
must be present in schema and their meaning. E.g. knowing that "pets" key is
present and leads to a relationship of a particular kind, with all the
implicit logic added and documented. And what if you want to get pet's owners
with some additional parameter, like only getting ones which are exclusively
yours? Would you need to edit that "pets" url adding
"&exclusively_owned=true"?

~~~
jfengel
I can think of one upside: it does make your keys more distinct. I can tell at
a glance that /pets/12345 is a different category of entity from
/people/98765\. That could aid in debugging. It's a bit like a type system.

And of course since it is a type system, it now means you've got yet another
type system to deal with in your universe. One with unknown and inconsistent
semantics, and no natural support built in. Conceivably those semantics could
be added and software built to support it, but rolling your own is going go
yield a lot of effort with much less benefit.

~~~
k_bx
Yeah, but it would essentially still be more of a "string id" rather than a
URL, e.g. you can just make your ID to look like "person-12345" and
"pet-52435" without tying it to URL. Showing as valid URL can be a "nice
artefact" to remove the need to lookup API docs for collection names.

------
sbr464
I don’t mind using a link, but I’d prefer to have both the exact id and the
link to avoid having to parse a link in an unreliable way to get the actual
id.

It’s interesting how GraphQL changes the base point of the article concerning
documentation/ease of api use.

In our GraphQL resolvers we typically add two fields, thing_id which resolves
to the id string, and thing which resolves an object that you can drill into
as desired.

I was starting to see a lot of GraphQL APIs only add “thing”, which meant you
had to do a lot of queries like below just to get the id.

    
    
      thing { id }

~~~
rileymat2
I suspect the link is the id, as in uniform resource identifier. So parsing
would be a mistake. At least on the client side.

~~~
mypalmike
When you are orchestrating an interaction between several services where they
are all using a shared identifier somewhere in their API, you need to extract
that identifier if it's embedded in a URI.

So tonight we're going to regex like it's 1999.

~~~
tuukkah
The identifier is not embedded in a URI, the identifier _is_ a URI: instead of
an integer id 1234 or a string id "1234", you can use a string id
"[http://example.com/id/1234"](http://example.com/id/1234"). Typically, one
service is responsible for creating the id but after that, it can be used to
refer to the same entity in any number of services. EDIT: What makes it better
than a free-format string id is that every person and tool knows how to de-
reference it / look it up.

~~~
jeltz
What determined which API owns a resource when the same resource is exposed in
mutlipe apis (e.g you have a REST API and a AMQP feed)? In some cases it is
obvious but in other cases it is far from clear.

I feel using urls as ids create more problems than it solves, especially if
you use absolute urls. I have worked with cases where the same ID was used in
3 or 4 different APIs. None of them clearly the master.

~~~
tuukkah
The URI doesn't have to be fully owned by any single microservice. The JSON
representation can more or less correspond to a HTML view and be served by the
UI, or a separate API facade can combine the relevant pieces of information
from various microservices.

------
geezerjay
Why did the author of this blog post decided to pass web links in resources
and completely ignored standard practices such as RFC 8288 which employs the
Link HTTP header?

[https://tools.ietf.org/html/rfc8288](https://tools.ietf.org/html/rfc8288)

Additionally, compact URIs (CURIES) are also widely used in this context.

[https://www.w3.org/TR/2010/NOTE-
curie-20101216/](https://www.w3.org/TR/2010/NOTE-curie-20101216/)

I feel that the author tried to reinvent HATEOAS but skipped a cursory
bibliographical review and jumped right into reinventing the wheel, and one
which has already been reinvented multiple times (HAL, JSON-LD, etc...)

~~~
solipsism
The post itself, and this response, illuminate a reality which we don't talk
about -- the set of standards around HTTP are a horrible mess. As a person who
in a past job has tried very hard to implement a "standards compliant" HATEOAS
API, the sheer mass of complexity and vagueness is just too much. It's easier
to just write something that works and which resembles something people are
used to than to wade through these horrible RFCs trying to follow standards
without good reason. It's like a horrible joke -- I wouldn't be surprised if
you got to the final RFC and it said "Just kidding! Congratulations on getting
this far, but this was just a test of your tolerance for pedantry."

This emperor has no clothes.

~~~
geezerjay
> the set of standards around HTTP are a horrible mess.

That's not the problem at all.

It would be the problem if the current state of the art was actually taken
into account and discarded for some reason.

Instead, the state of the art (or even basic standard practices) is
systematically ignored, and we end up seeing the same old wheel being
supposedly invented again and again by people who fail to perform the
flimsiest cursory bibliographical research on any given topic, and instead
invest the bulk of their time announcing their poor reinvention of the wheel.

The web is based on linking. It's a very basic concept. Linking resources is
not a new problem. What line of reasoning can possibly lead anyone to believe
that this specific problem has never been researched by anyone before us, thus
it's a sensible idea to simply dive head-first into coming up with a proposal
that completely ignores any prior work?

~~~
solipsism
Nope, the current state of affairs is an indictment of the "standards". It
should be easy and obvious to implement something according to the standards,
but it's not. I'm a relatively smart guy, and I've tried. You very quickly
reach the point where you ask, "why the fuck am I doing this?" and just do
what makes sense.

Let's take RFC8288 as an example since you've brought it up. Where in that RFC
does it discuss _why the fuck_ I would want to put an API link in the headers
instead of the body?

The fact is that this RFC isn't "the standard". The standard thing is to put
API links in the body with other attributes, and it's the standard because
that's what everyone does, and because that's what makes sense. This RFC is a
hammer for HTTP pedants to hit people over the head with.

 _The web is based on linking_

And where to we expect those links to appear? In the header?

~~~
niftich
FWIW, RFC 8288 was preceded [1] by RFC 5988, and RFC 5988 [2] says in its
section '1\. Introduction':

"A means of indicating the relationships between resources on the Web, as well
as indicating the type of those relationships, has been available for some
time in HTML [W3C.REC-html401-19991224], and more recently in Atom [RFC4287].
These mechanisms, although conceptually similar, are separately specified.
However, links between resources need not be format specific; it can be useful
to have typed links that are independent of their serialisation, especially
when a resource has representations in multiple formats."

"To this end, this document defines a framework for typed links that isn't
specific to a particular serialisation or application. It does so by
redefining the link relation registry established by Atom to have a broader
domain, and adding to it the relations that are defined by HTML."

[1] [https://tools.ietf.org/html/rfc8288](https://tools.ietf.org/html/rfc8288)
[2] [https://tools.ietf.org/html/rfc5988](https://tools.ietf.org/html/rfc5988)

~~~
yawaramin
I might be missing the obvious, but I don't see in your quote the answer to
the question:

> Where in that RFC does it discuss _why the fuck_ I would want to put an API
> link in the headers instead of the body?

~~~
niftich
The answer is in the part that says "it can be useful to have typed links that
are independent of their serialisation, especially when a resource has
representations in multiple formats".

In HTTP, URLs locate a 'resource'. Then you and the server do content
negotiation, implicit and/or explicit, to select a 'resource representation'.
Think of these as different formats for the same conceptual thing identified
by the URL. Some formats like HTML can support hypermedia that can have
embedded links. Some, like 'text/plain' or 'image/gif', can't.

Link headers allow links from the current resource to other resources to be
communicated even if the chosen representation can't communicate links in its
body.

~~~
yawaramin
Got it, thanks. How do you define 'the chosen representation can't communicate
links in its body'?

~~~
niftich
You as the client try to GET /my-receipts/20190512-1 from Fancy Receipt
Scanning Service, and content-negotiate with an Accept header to "text/plain"
or "image/gif" (e.g. to get a plain copy or a scan). There's no agreed-upon
way of communicating links in plain text or GIF, so Fancy Receipt Scanning
Service can't serve you a GIF scan of your receipt that links to a product
page for every item you bought.

If you accepted "text/html", it could have served HTML that embedded these
links within the response body, but you didn't accept "text/html".

It can choose to send links as headers, if it still wishes to communicate
links.

~~~
yawaramin
That's fair, but if I'm defining an API that serves, say, JSON, I can define a
schema for it and tell my clients what things mean in the schema, including
which things are links.

------
westurner
A thing may be identified by a URI (/person/123) for which there are zero or
more URL routes (/person/123, /v1/person/123). Each additional route
complicates caching; redirects are cheap for the server but slower for
clients.

JSONLD does define a standard way to indicate that a value is a link: @id
(which can be specified in a/an @context) [https://www.w3.org/TR/json-
ld11/](https://www.w3.org/TR/json-ld11/)

One additional downside to storing URIs instead of bare references is that
it's more complicated to validate a URI template than a simple regex like \d+
or [abcdef\=\d+]+

~~~
Twisell
And on a more fundamental standpoint I get that disk space is cheap theses
days. But you just doubled, if not worse, the storage space required for a key
for a vague reason.

It may never make any difference on a small dataset, where storage was anyway
unaware of differences between integer and text. But it would be hiding in the
dark. And maybe in a few year a new recruit will have the outstanding idea to
convert text-link to bigint to save some space...

------
Illniyar
The idea of using uris instead of keys is not a new one (as has been mentioned
by other commenters). Every few years the idea gets a resurgence of people who
say that REST apis should be HATEOS and that _we are doing it wrong_.

It seems obvious that the cost-value for this is simply not there, if it was
good enough, you'd see developers requesting it and many more vendors
implementing it. So far I haven't seen any recent changes that might skew the
cost-value towards the uri's favor, only the opposite (cue GraphQl).

Using uris have little benefits, but it does have the following problems:

As a user of the api:

* You need to keep an arbitrary length key in your database if you save references. It can cause some issues with certain setups (less so these days though).

* If you keep the entire URI as identity, then you can't use multiple endpoints. For instance lots of companies have an endpoint for production and one for reports - using URI for one endpoint in another is quite awkward.

* Working with queries is troublesome, especially with get request. Consider searching for all transaction of a specific account, where the account's identifier is `[https://api.google.com/v1/account/123`](https://api.google.com/v1/account/123`)

* Upgrading to a new version of the api (one with a different url like v1/v2) now not only requires you to change your code to work with the new version, but also migrate all previous ids you kept in your database, which is a much different and more error-prone issue then simply changing code.

~~~
mpnally
One simple strategy is not to change your approach to storage of IDs, which
can continue to be "simple" keys. If you use that strategy, the only change
from a "conventional" application is that the server is doing all the
URL<->simpleId conversions instead of pushing that responsibility onto the
client. Another good strategy is to store Ids in the form that the URI spec
calls "path-absolute" URIs — basically you lop off the scheme and authority.
This strategy works well, but may require a bit more care, and may cause
problems if you have to integrate with other tables that have a different
approach to keys.

------
kartan
The article knowledge has been lost to time. In "relational databases" you
always name the foreign key as the relationship between tables.

From "A Practical Guide to Relational Database Design" from the year 2000.
"Each relationship line should carry a pair of descriptive elements, which
define the nature of the association between entities. A name is a single word
or descriptive phrase; it should always contain a verb such as: owns, owned
by, holds, administered by, etc. Examples from our simple model are: A PART is
sold on an ORDER LINE. An ORDER LINE is placed for a PART."

But, this has been lost because the practicality is that it is hard to know
what is the element. As other comments points.

Probably the best is both worlds: PersonId_Owner. PersonId_Veterinary. Or
something similar.

It seems that such a discussion should have been solved decades ago. And here
we are. :)

------
rhacker
I feel like the author has never used graphql. We're also just finally
graduating past REST to something more meaningful. This advice feels 15 years
late and now totally wrong. An API shouldn't be tied to a protocol like http,
it should be able to move on to other things.

Ahh I was correct:

> I have never used GraphQL, so I can't endorse it, but you may want to
> evaluate it as an alternative to designing and implementing your own API
> query capability.

You really shouldn't write this giant article without having tried that.

------
chvid
Hypermedia As The Engine Of Application State (HATEOAS)

[https://en.wikipedia.org/wiki/HATEOAS](https://en.wikipedia.org/wiki/HATEOAS)

The idea has been around for a while; I personally don't think it is a good
idea.

There is even a content type (or two) for it: application/hal+json and
application/hal+xml.

[http://stateless.co/hal_specification.html](http://stateless.co/hal_specification.html)

~~~
niftich
The article's recommendations don't achieve HATEOAS, because even though the
foreign-key IDs are replaced with URLs, they're not actually links because
they don't specify an explicit relationship.

Instead, the relationship between the response document and the URL's target
is implicit, probably guessed from the naming of the key or maybe noted in the
response document's definition.

The point of HATEOAS is that a client who understands the meaning of certain
link relations (aka "rels"), such as ones in the IANA registry [1], can
interact with these referred-to resources using the Standard Interface (of
GET, POST, PUT, etc).

Only in the section where the article talks about ways to express links in
JSON do link relations appear.

[1] [https://www.iana.org/assignments/link-relations/link-
relatio...](https://www.iana.org/assignments/link-relations/link-
relations.xhtml)

~~~
mpnally
In the style of JSON I like to use, and whih GitHub and Google Drive use, the
relationship name is given by the JSON name. So if you see 'owner:
/person/12345', then 'owner' is the relationship name. There are other JSON
styles for expressing the relationship name — the blog post mentions some of
them. You might quibble about whether these names are the names of the
relationship, or just names of one end of the relationship.

------
siscia
I just don't understand why mixing two different concepts at two different
abstraction levels only for some, apparent, simplicity.

On one level we got ID unique identifier of a resource, on another level we
have URL, how to get a specific resources.

They are just different things that shouldn't be mixed.

What if tomorrow I want to get the same resource via graphql? Or in a message
bus?

~~~
frosted-flakes
Why can't the ID also be a link? Serious question. I don't see any real
downside to it.

If the ID is also a link, it is guaranteed to be globally unique (like a Relay
Node ID in GraphQL).

If you want to get the same resource via GraphQL, just use the same URL-ID. In
fact, the author mentioned the possibility of base64-ing the link to prevent
clients from relying on its structure, which is also a common pattern with IDs
in GraphQL.

~~~
siscia
Is not that they can't, it is just that link are not designed to be ID.

The ID is the maximum cardinality of an entity (informally the smallest set of
values of such entity necessary to uniquely identify it), the link is how to
find the entity.

What happen if you want to deprecated your API?

There are other way to share the link of a resource,like use headers.

Then again, from a strictly practical point of view in 99.9% of the case it
won't change anything, but:

1\. Eventually there will be cases where it does matter, and then it will be
an huge mess.

2\. Why shut yourself a door only to don't read an header and obtain exactly
the same information?

------
EugeneOZ
What is the source of knowledge for the client about fields, where they can
read link to the entity?

For human it's obvious that dog has an owner, so field "owner" should be used,
but for code - you need to write it, "document it". So if you're going to
"document" every field containing link to external resource, you'll end up
with even more code, than just "documenting" API endpoints.

Also, pretty often you need multiple IDs of entities to send POST/PUT request
- just to create a relation.

POST /adoption, owner_id=5, dog_id=7.

How should it look with links? Will it be issue for the server to parse them?
And it's just simple case with 1 to 1 relation, sometimes you need to add sets
of objects to another entity.

It's a really bad advice and after reading this I'm not sure I should trust
other articles from that source.

~~~
frosted-flakes
> Also, pretty often you need multiple IDs of entities to send POST/PUT
> request - just to create a relation.

> How should it look with links?

Like this:

POST /adoption, owner=/people/5, dog=/pets/7

> Will it be issue for the server to parse them?

Why would it be?

~~~
EugeneOZ
Because it's more difficult than parse "5" and "7", especially when you need
to parse not only numeral IDs. "More difficult" always means "more bugs" and
"more vulnerabilities".

There is no single reason for this complication. The ONLY motivation author
had - less knowledge about API endpoints on the client. But simultaneously it
means more knowledge about the fields where links are stored, so it doesn't
save nothing.

~~~
imtringued
I believe "so it doesn't save nothing." should be written as "so it doesn't
save anything." [0]

Feel free to ignore me.

[0] [https://www.quora.com/Is-the-sentence-dont-do-nothing-
correc...](https://www.quora.com/Is-the-sentence-dont-do-nothing-correct)

~~~
EugeneOZ
thanks :)

------
kabes
The document also forgets that API's are not read-only. So let's say you have
users and usergroups and you can request a usergroup with its list of users
and you can add users to usergroups.

If you use links for read, you should also use them for writes, otherwise it's
quite inconsistent. So now you need to add a lot of parsing everywhere to
extract the id's out of the urls, just for the sake of being more dogmatic

~~~
mpnally
I have implemented many APIs in this style, both read and write. Depending on
the design of the storage layer, the server may or may not have to parse ids
out of URLs. The client never has to; for the client, the URL is the only Id.

------
whack
I've literally spent 2 years working on a project that did exactly what this
article is recommending. There were some places which needed the relative-url
as an identifier, and other places which needed the "database id" as the
identifier. We constantly had to extract the id from the URL, or convert the
id into a URL, and keep a mental map of which format each input was using, and
which format was needed for each output. It was a mess. I would personally not
recommended this at all.

~~~
mpnally
If some parts of your API used database keys as identifiers and other parts
used URLs, then I can see that could be confusing. All one or all the other
would probably be better.

~~~
whack
Anytime you're interacting with the database, you'll need to use database
keys. Anytime you're interacting at the API interface level, you'll need URLs.
Anything in the middle is then going to be a gray area, especially when you
have multiple people working on this implementation together.

------
wvenable
The caveats section of this article is longer than the content -- it makes a
better case for _not_ using links as keys.

------
anbop
Basically, advocating for dynamic typing rather than static typing, across an
API boundary. You’ll save code constructing API requests but need to create a
lot of application logic to handle an owner link and pet link separately,
since they have different semantics.

------
gridlockd
No.

What's the point? None of this is useful to me, all of this is extra
complexity. Why would I want to expose every addressable entity through URLs
and HTTP? That's not what IDs are for.

I'm aware that this fits into the whole REST idea. I still don't care.

------
austincheney
I am surprised the article didn't mention RDF. In every data facet of RDF the
data is uniquely identified by URI. In the case of RDF the URI is merely a
unique identifier that can resolve to a HTTP resource, but doesn't have to.

------
vasilakisfil
The fact that JSON is just a format standard and doesn't have specified
components (like links etc) but instead we have to built those on top has cost
us a lot in APIs. Btw, according to RFC 8288 Web Linking (and before that
5988), a link consists of 3 parts + 1 optional part:

"In this specification, a link is a typed connection between two resources and
is comprised of:

    
    
       o  a link context,
       o  a link relation type (Section 2.1),
       o  a link target, and
       o  optionally, target attributes (Section 2.2).
    
       A link can be viewed as a statement of the form "link context has a
       link relation type resource at link target, which has target
       attributes".
    
       For example, "https://www.example.com/" has a "canonical" resource at
       "https://example.com", which has a "type" of "text/html".

"

That's why you need a standardized link component that is globally
accepted/understood that takes into account all parts of the linking, instead
of having various ways depending on the API/JSON-based Media Type to
communicate that something is a link.

------
vbezhenar
I used links but I'm gonna rewrite this code to simply pass IDs. The reason is
simple: I need additional configuration for my server to know its hostname and
I don't want to do that. May be my server even have few different hostnames
for different clients? So I must parse client request and extract Hostname?
But it's served via reverse-proxy, so I must do some complex configurations to
pass this information. So many issues. But client knows perfectly well which
server he's talking to, so he can just append server-base and id. Yes, client
must know about its structure, but it's nonsense that client can somehow learn
something. I'll code that anyway.

May be it makes sense when you're writing an API and some different person
writes a client and she's so shy that she don't want to even ask you. Yeah,
she can inspect answer and find out that this seems like a link to query
further. I never was in that situation, I was always building all software
myself, so for me this does not make sense.

------
theptip
I'm not sure about the verdict on URL versioning. I've used header versioning
extensively and while flexible, it also carries some big downsides, mainly
that it's confusing for new developers, and makes it real hard to casually
explore the API in a browser (bad DX). I'm also not sure you do want to
encourage mixing v1 and v2 API representations; I have certainly seen cases
where it makes progressive upgrade easier, but it can also bring
inconsistencies, so having a default new integrator path of "start at v2/login
and use whatever links you get" is appealing.

I do like the idea from Stripe of having Accept header versioning, but pinning
every new client to default to the newest GA version. Gets around most of the
DX concerns I raised, but it's a bit more machinery to wire up.

------
perfunctory
One advantage I see is that now you can do

    
    
      /pets?owner=/people/98765
    
      or 
    
      /pets?owner=/org/98765
    

which makes your api more polymorphic.

Having said that I don't think URL is the right term to describe this. It's
more like a <type, id> tuple.

------
miguelmota
I wasn't fully convinced by this article. Language specific API wrapper
clients can abstract all these complexities. Having links for IDs felt very
unnatural but I guess that's because I have never came across an API that uses
links like the article suggested

------
asavinov
Conceptual and data modeling aspects of this problem are discussed in [1]. It
compares links with joins (and foreign keys) by proposing a solution (concept-
oriented model) which does not use joins at all but rather relies on links
only.

Essentially, a foreign key is viewed as a relational workaround for
representing links with some significant drawbacks and the question is why not
to use links directly without relational wrapping.

[1] Joins vs. Links or Relational Join Considered Harmful:
[https://www.researchgate.net/publication/301764816_Joins_vs_...](https://www.researchgate.net/publication/301764816_Joins_vs_Links_or_Relational_Join_Considered_Harmful)

~~~
tuukkah
That seems like a highly relevant aspect to explore. For example, GraphQL does
not specify joins and nesting is semantically a reference (directed link in
the graph) instead. Any update on the cliffhanger?

 _> Yet, classical references miss some properties which are of crucial
importance for data modeling. How links can be revisited in order to overcome
these drawbacks will be our focus for future research._

------
gigatexal
Sometimes I wish REStful/REST the whole idea was a lot more opinionated. Sure
you can have opinionated frameworks but nothing is stopping you from using a
patch like I would a delete... (not the best example but you get the gist).

------
abetlen
I think the issue brought up in this blog post pales in comparison to the two
biggest problems faced when working with REST APIs: querying for nested data,
and the limitations of CRUD interfaces to model complex behavior.

~~~
treve
I've been building REST apis for 13 years and never had either of these
problems. I might be reading too much into this, but it sounds like you enjoy
GraphQL or RPC-like apis and have trouble mapping their respective concepts
1:1 to REST. You shouldn't.

These architectures aren't equivalent alternatives. Pick the right tools for
the job, and use the right tool appriately.

~~~
abetlen
No I work mostly with REST apis and I find that api consumers don't really
care about having nice relative links or references to self. If anything the
popularity of GraphQL indicates that they want more flexibility than is
usually available through REST and posts like this are the ones really
addressing non-problems.

~~~
treve
> If anything the popularity of GraphQL indicates that they want more
> flexibility than is usually available through REST

I absolutely agree. GraphQL clearly addresses a pain point, or a gap. I just
don't think it should be seen as a replacement. Pick the right tool for the
job.

------
schnable
How do you write the article and never mention REST, hypermedia or HATEOS
once?

~~~
mpnally
It required some discipline. I had to catch myself several times. The reason I
avoided those terms is that I wanted readers to focus on one simple idea and
not get distracted by all the baggage those terms bring with them. You can see
by the comments that I wasn't totally successful.

------
i386
Why on earth would you blow out your request size for the sake of purity?
Calling GET /pets is going to return a lot of instances of pet with very
similar URLs.

------
currriuosly
This is what Django REST framework had done right for years.

~~~
rich-tea
Indeed. Hyperlinked serialisers are built in and super easy to use. And it
makes so much sense when you use DRF's API browser too.

------
hit8run
JSONAPI Specification also makes use of URLs in links to resources:

[https://jsonapi.org](https://jsonapi.org)

~~~
Seb-C
I have implemented JsonApi a few times and so far it is the best one. It
solves all problems I had about APIs in a very nice way. The `include`
strategy is very simple and effective for dealing with relationships.

------
vbsteven
What really convinced me about HATEOAS and links was the first time I used a
HAL browser and started clicking around to discover an API using only its
entry point and navigating from there.

From that point on I try to use it as much as possible. A typical API response
for my projects looks like this:

    
    
      {
      "id": "3ccf0f1b-dd3f-48d9-911a-ddf479078c37",
      "name": "Quantus Tasks",
      "description": "Quantus Tasks Desktop Application",
      "license_key_type": "alphanumeric_32",
      "created_at": "2019-05-12T10:45:42.089406Z",
      "updated_at": "2019-05-12T10:45:42.089406Z",
      "_links": {
        "self": {
          "href": "http://localhost:8000/v1/applications/3ccf0f1b-dd3f-48d9-911a-ddf479078c37"
        },
        "licenses": {
          "href": "http://localhost:8000/v1/applications/3ccf0f1b-dd3f-48d9-911a-ddf479078c37/licenses"
        },
        "templates": {
          "href": "http://localhost:8000/v1/applications/3ccf0f1b-dd3f-48d9-911a-ddf479078c37/templates"
        },
        "apikeys": {
          "href": "http://localhost:8000/v1/applications/3ccf0f1b-dd3f-48d9-911a-ddf479078c37/apikeys"
        }
      }
      }
    

It still has the ID field in there for cases where the client needs to store
the id itself but it should not be used to template URI's for related
resources, the links are there for that.

~~~
nebulous1
I've always been a bit undecided on this. I can see some obvious upsides, but
on the other hand (apart from the downsides mentioned in the article and
elsewhere) you've added 500 bytes to this entity for functionality that's only
useful to the developer. Is the ability to click around in a HAL browser
instead of a Swagger document worth the verbosity? Is there an argument that
we might produce clients that can meaningfully deal with new links being
presented without a developer being involved? I'd be surprised if that was
realistic.

~~~
vbsteven
Yes, 500 bytes extra but it's not only for the developer discovering the API
in a browser. If the client developer is exploring the API and then manually
implementing an API client something is not right.

Hal clients for this use case do exist. you point them to an entrypoint and
they can discover and generate the necessary code/methods for interacting with
the API.

For example: [https://github.com/pezra/hal-
client](https://github.com/pezra/hal-client)

------
sam0x17
I think I am missing the core concept here. This still uses IDs, only now you
have to grep them out of a URL construct instead of just getting them
directly?

I don't get the intent at all here, but I have a suspicion whatever problem
this tries to solve is better solved by UUIDs or by doing nothing out of the
ordinary.

~~~
treve
The URL doesn't contain the ID, it _is_ the id. If you stop treating the URL
as an opaque string but start parsing things out, you are definitely not
getting any benefits.

One benefit of using urls as ids is that it no longer is just an id, it also
describes where you can get it's representation.

------
ragerino
Reminds me of HATEOAS see here:
[https://en.wikipedia.org/wiki/HATEOAS](https://en.wikipedia.org/wiki/HATEOAS)

Also RDF endpoints usually use resolvable URI's to connect concepts and
objects with each other.

------
polskibus
Is the main reason for this is that crawlers could figure out and index more
by themselves?

------
tveita
No one thinks twice about using links for images. You wouldn't make an API
that specified images as "image id 2345, which the client can find at
/images/{id}"

~~~
minitech
I would and have…. If you’re in a position to specify that images can
consistently be identified by a small key, that’s nice and simple. Go for it.

~~~
EugeneOZ
Key of 10 symbols from (latin alphabet + numbers) have 3656158440062976
possible combinations.

~~~
minitech
That’s a fun fact, but what does it have to do with this?

------
coding123
This is all assuming REST is still a good idea.

------
carmate383
Why on earth would one trade off a short, static unique identifier for a
potentially long, dynamic "link" that essentially binds all data to some
crappy API that will be outdated in a few years? Is it really _that_ hard to
use keys?

------
the_arun
In micro services world this makes perfect sense. In the legacy land - this is
slightly tricky as dependent application (where our foreign key points to) may
or may not be in services world. But I get the idea.

~~~
the_arun
I don't know what made my above comment to get negative votes (-3). I was
sharing an honest opinion here.

