How can I avoid hardcoding URLs in a RESTful client/server web app with deep linking?
Asked Answered
P

3

2

I'm working on a SPA which is a client to a RESTful web service. Both the client and server are part of the same project, i.e. I can modify the code for both sides freely. I've been reading up on RESTful API design to try and make sure I'm doing everything the "right" way. One of my takeaways from reading is that a RESTful service should publish hyperlinks so clients can access more information, and that clients should have no hardcoded information about service URLs other than an entry point. Using hyperlinks allows the client to be more flexible in the event that the server makes URL changes.

However I can't figure out how this architecture is supposed to work when users are allowed to link to a specific client state. For example:

One of the views is a list of books available for purchase. The client sets the browser's location to /books/ to identify this page, and the backend data comes from an endpoint /api/books/, retrieved from an API entry point that publishes that URL. The service URL responds with a JSON document like this:

[
    {"title": "The Great Gatsby",
     "id": 24,
     "url": "http://localhost/api/books/24/"},

    < and so on >

]

The client uses this to generate readable links that, when clicked, go to a detailed view of a single book. The browser's location is updated to /books/the-great-gatsby/24/ so users can bookmark this view and link to it.

How does the client handle when users click this link directly?? How would it know where to get the information for this book without having a hardcoded URL?

The best I could come up with is the following sequence of requests:

  1. GET /api/ - view which services are available (to find there are books at all)
  2. OPTIONS /api/books/ - view a description of what operations are available on books (so e.g. it can make sure it can find books by ID)
  3. GET /api/books/?id=24 - See if it can find a book with an ID that matches the ID in the browser's location.
  4. GET /api/books/24/ - Actually retrieve the data

Anything shorter would imply that the client has hardcoded knowledge of the API's URLs. However, from a web app point of view, this seems grossly inefficient.

Is there some trick I'm missing? Is there a way for the client to "know" how to get more detail about book ID 24 without somehow having the /api/books/24/ endpoint hardcoded?

Physicist answered 16/5, 2017 at 21:3 Comment(5)
If you let users bookmark a URL then you need to support that location on your server. I wouldn't call it 'hardcoded' information.Oxide
maybe I don't understand your architecture: If there's client-side code that generated the URL, then it is up to the client to re-produce the content. So you are wondering where to store http://localhost/api/books/24/ in this case?Oxide
@JochenBedersdorfer exactly - I don't see an alternative to hardcoding that URL builder in my Javascript code, so that I can efficiently render the /books/the-great-gatsby/24/ view.Physicist
if the server generates the page initially, there's nothing preventing it from adding http://localhost/api/books/24/ as a link tag in its headOxide
The server doesn't render any pages, it just serves the JS app. Even if it did, a link tag like that would still couple the client and server together (it would just be the server that knows how to parse the client locations)Physicist
O
2

if you request this resource /books/the-great-gatsby/24/ from the server, the server should respond with something specific to that URL. Currently, you are probably analyzing window.location which is a bit of a hack.

If /books/the-great-gatsby/24/ is static content, then you have very little choice: You store the client's current state explicitly somewhere (i.e. /books?data=api/books/24 or implicitly /books/the-great-gatsby/24/ which then leads to the client having to know how to translate that to an API resource.

The RESTful way is to use hypertext to indicate where any related resources (i.e. your data to render is) are which makes a tag an appropriate choice.

i.e. ditch the static content, and render /books/the-great-gatsby/24/ with a <head><link href="api/books/24" ....></link></head>

However, if you always retain control of your client side and don't plan to publish the API to third parties, you might be more productive ditching RESTful and just go RESTish.

Oxide answered 17/5, 2017 at 0:7 Comment(0)
P
1

The Resource URL Locator pattern

In this answer: user is (the human interacting with) the internet browser, client is the Single Page Application (SPA) and server is the REST API.

Deep linking is a convenience of the client to the user; the client itself may still not have knowledge of the server's URLs, so the client must start at the root URL of the server. The client uses content negotiation to indicate which media type it needs. The first request of the client to the server when bootstrapping itself could be as follows:

GET /?id=24 HTTP/1.1
Accept: application/vnd.company.book+json

Optionally, the client uses the id querystring parameter as a hint to the server to select the specific resource it is looking for.

When the server has determined which resource the client is looking for it can respond with a redirect to the canonical URL of the resource:

HTTP/1.1 303 See Other
Location: https://example.com/api/books/24

The client can now follow the redirect and get the resource it needs to bootstrap the application.

Pruchno answered 22/11, 2022 at 9:27 Comment(9)
REST APIs should never have typed resources significant to clients! Instead, use content type negotiation to exchange the state in a format that can be processed by clients. The media-type is the "contract" both, server and client, will adhere to. Through client side mappings the data following a certain media-type can then get "converted" to usable objects on the client side. REST is all about those indirections!Dorsal
Good point, thanks for the link @Roman. The client still needs a way to select the right media type, but that can be hard-coded in the application. I will update my answer when I have the opportunity.Pruchno
A client does not need to select or hardcode anything. It just sends its capabilities to the server, as part of the Accept request header and the server will then chose the most appropriate representation format or a 415 Unsupported Media Type response in case of no overlap. Of course, a client needs to support those media-types though, which may be added through plug-ins or the like dynamically. It may even add hints to the server on which media-types a client prefers over others.Dorsal
@RomanVottner I modified the answer; I think this is a better solution.Pruchno
I don't think the Referer header can be set by a SPA. It would also be a bit wild to let the response of the API depend on it. The implication is also that the server know needs to understand the client namespace, which is especially weird if there's multiple (sometimes 3rd party) clients.Regularly
@Regularly Agreed. Another solution I thought about is to let the client pass an (optional) identifier through a querystring parameter, because the client is capable of determining the identifier supplied through a client-side URL. How does that sound to you?Pruchno
@BorisEetgerink Yeah the way I've solved this is that our client-side namespace is something like http://spa.example/go/[backend uri]. Where [backend uri] may be relative (to the backend). By far the easiest way to handle this and a super straightforward mapping.Regularly
@Regularly That's a good compromise and a straightforward solution!Pruchno
@Regularly Can you please add your approach as an answer? I will upvote it. I now consider it as something like the continuation of the application state where you left off. Revisiting a bookmark is something like continuing where you left off. I consider using the content type now as too unreliable. It only works if you have many very specific media types, not if you have a few generic media types.Pruchno
P
0

@Evert's comment got me thinking. Isn't a deep link or a bookmark just a continuation of application state from a previous point in time? It doesn't really matter how much time has passed after the previous application state transition.

You could say that the 'current' application state in HATEOAS is the last followed link. The current state must be stored somewhere and it might as well be stored in the application URL.

Starting the application at a deep link indicates to the application that it should rebuild the application state by requesting the resource indicated by the application URL. If the resource is no longer available or has moved, the server should respond with a 404 Not Found or 301 Moved Permanently respectively.

With this approach the server is still in control of the URLs. The application follows the hypermedia links in the server's responses and doesn't generate URLs itself.

Pruchno answered 3/12, 2022 at 14:52 Comment(1)
Slightly off-topic, but relevant on how to make a SPA RESTful: world.hey.com/boriseetgerink/a-restful-spa-88b75a9d.Pruchno

© 2022 - 2025 — McMap. All rights reserved.