(This is a cross post to Stack Overflow:
would like to send an answer here and there, that would be great! If not, I
might excerpt some of the answers I receive on list and post them on Stack
Overflow.)
I'm interested in how to implement HATEOAS with a single page application (SPA)
that is using `pushState`. I want to preserve deep linking so that users can
bookmark URLs within the SPA and revisit them later or share them with other
users.
For concreteness, I'll present a hypothetical example. My single page
this URL in a browser, it downloads an SPA and bootstraps. The SPA looks at the
browser's current `location.href` in order to figure out what API resource to
fetch and render. In the case of the root URL, it requests
{
}
Now the SPA renders a user interface that displays these two link relations to
the user and the user can click an "Employees" button to view employees or
"Offices" to view offices. Let's say the user clicks "Employees". The SPA needs
to `pushState()` some new href, otherwise this navigation decision will not
appear in the history and the user will not be able to use the Back button to
return to the first screen.
**This presents a small dilemma: what href should the SPA use?** Clearly, it
valid resource within the SPA, its not even in the same origin and `pushState()`
throws exceptions if the new href is in a different origin.
The dilemma is [perhaps] easily resolved: the SPA is aware of a link relation
called `employees`, so the SPA can hard code a URL for this resource:
collection. The API returns a result like this:
{
"employees": [
{
"name": "John Doe",
},
{
"name": "Jane Doe",
},
...
]
}
*There are more than two results, but I've abbreviated with an ellipsis.*
The SPA wants to displays this data in a table where each employee name is a
hyperlink so that the user can view more details about a specific employee. The
user clicks on "John Doe". The SPA now needs to display details about John Doe.
It can easily obtain the resource using the link relation, and it might get
something like this:
{
"name": "John Doe",
"office": {
"location": "Washington, DC",
},
"supervisor": {
"name": "Jane Doe",
},
}
**But now the same dilemma rises again: what URL should the SPA choose to
represent this new internal state?** This is the same dilemma as above, except
this time it's not possible to hardcode a single SPA URL, because there are an
arbitrary number of employees.
I think this is a non-trivial question, but let's do something hacky so we can
keep moving forward: the SPA constructs a URL by replacing 'api' in the hostname
with 'www'. It's awfully ugly, but it doesn't violate HATEOAS (the SPA URL is
only used client side) and now the SPA can
approach, the SPA can display navigation options for any link relation, and the
user can explore related resources: where is this person's office? What is the
supervisor's phone number?
**This still doesn't solve deep linking.** What if the user bookmarks
revisits this bookmark later on? Now the SPA has no recollection of what the
underlying API resource was. We can't reverse the substitution (e.g. replace
'www' with 'api') because that's not HATEOAS.
The HATEOAS mindset seems to be that the SPA should request
employee profile, but there's no fixed link relation to get from `employees` as
a collection to `John Doe` as a specific employee, so that won't work.
Another HATEOAS approach is that the application could bookmark URLs that it has
discovered. For example, it could store a hash table that maps previously seen
SPA URLs to API URLs:
{
}
This would allow the SPA to find the underlying resource and render the UI, but
it requires persistent state across sessions. E.g. if we store this hash in
HTML5 storage and the user clears their HTML5 storage, then all of the bookmarks
would break. Or if the user sends this bookmark to another user, that other user
wouldn't have this mapping of SPA URL to API URL.
**Bottom line: implementing a deep linking SPA on top of a HATEOAS API feels
very awkward to me. In my current project, I've resorted to having the SPA
construct almost all of the URLs. As a consequence of that decision, the API
must send unique identifiers (not just URLs) for individual resources so that
the SPA can generate good URLs for them.**
Does anybody have experience doing this? Is there a way to satisfy these
seemingly contradictory criteria?