Search code examples
restsingle-page-applicationapi-designhateoas

How to make initial request for nested resource from self describing REST API


Background:

I have a single page application that pulls data from a REST API. The API is designed such that the only URL necessary is the API root, ie https://example.com/api which provides URLs for other resources so that the client doesn't need to have any knowledge of how they are constructed.

API Design

The API has three main classes of data:

  • Module: Top level container
  • Category: A sub-container in a specific module
  • Resource: An item in a category

SPA Design

The app consuming the API has views for listing modules, viewing a particular module's details, and viewing a particular resource. The way the app works is it keeps all loaded data in a store. This store is persistent until the page is closed/refreshed.

The Problem:

My question is, if the user has navigated to a resource's detail view (example.com/resources/1/) and then they refresh the page, how do I load that particular resource without knowing its URL for the API?

Potential Solutions:

Hardcode URLs

Hardcoding the URLs would be fairly straightforward since I control both the API and the client, but I would really prefer to stick to a self describing API where the client doesn't need to know about the URLs.

Recursive Fetch

I could fetch the data recursively. For example, if the user requests a Resource with a particular ID, I could perform the following steps.

  1. Fetch all the modules.
  2. For each module, fetch its categories
  3. Find the category that contains the requested resource and fetch the requested resource's details.

My concern with this is that I would be making a lot of unnecessary requests. If we have 100 modules but the user is only ever going to view 1 of them, we still make 100 requests to get the categories in each module.

Descriptive URLs

If I nested URLs like example.com/modules/123/categories/456/resources/789/, then I could do 3 simple lookups since I could avoid searching through the received data. The issue with this approach is that the URLs quickly become unwieldy, especially if I also wanted to include a slug for each resource. However, since this approach allows me to avoid hardcoding URLs and avoid making unnecessary network requests, it is currently my preferred option.

Notes:

  • I control both the client application and the API, so I can make changes in either place.
  • I am open to redesigning the API if necessary

Any ideas for how to address this issue would by greatly appreciated.


Solution

  • Expanding on my comment in an answer.

    I think this is a very common problem and one I've struggled with myself. I don't think Nicholas Shanks's answer truly solves this.

    This section in particular I take some issues with:

    The user reloading example.com/resources/1/ is simply re-affirming the current application state, and the client does not need to do any API traversal to get back here.

    Your client application should know the current URL, but that URL is saved on the client machine (in RAM, or disk cache, or a history file, etc.)

    The implication I take from this, is that urls on your application are only valid for the life-time of the history file or disk cache, and cannot be shared with other users.

    If that is good enough for your use-case, then this is probably the simplest, but I feel that there's a lot of cases where this is not true. The most obvious one indeed being the ability to share urls from the frontend-application.

    To solve this, I would sum the issue up as:

    You need to be able to statelessly map a url from a frontend to an API

    The simplest, but incorrect way might simply be to map a API url such as:

    http://api.example.org/resources/1
    

    Directly to url such as:

    http://frontend.example.org/resources/1
    

    The issue I have with this, is that there's an implication that /resource/1 is taken from the frontend url and just added on to the api url. This is not something we're supposed to do, because it means we can't really evolve this api. If the server decides to link to a different server for example, the urls break.

    Another option is that you generate uris such as:

    http://frontend.example.org/http://api.example.org/resources/1
    http://frontend.example.org/?uri=http://api.example.org/resources/1
    

    I personally don't think this is too crazy. It does mean that the frontend needs to be able to load that uri and figure out what 'view' to load for the backend uri.

    A third possibility is that you add another api that can:

    • Generate short strings that the frontend can use as unique ids (http://frontend.example.org/[short-string])
    • This api would return some document to the frontend that informs what view to load and what the (last known) API uri was.

    None of these ideas sound super great to me. I want a better solution to this problem, but these are things I came up with as I was contemplating this.

    Super curious if there's better ideas out there!