Links
1 Overview
Links are useful for:
- Discoverability
- Lazy-loading
- Hiding implementation details
Let’s say we’re loading a Post and its “Top Comments”. On Day One, we might eager load the data like so:
/posts/123?include=top_comments
This fetches all the data in a single request. But after some UI testing, we decide to add a “show comments” button. This way our page can load more quickly - it only needs to load the Post initially, and loading Top Comments can be deferred. We want to lazy load the relationship.
How would we do this? We could bake this logic into our next request:
/comments?filter[post_id]=123&filter[upvotes][gte]=100
But this requires the client to have knowledge of what a “Top Comment” is. If this logic ever changed, we’d have to update every client - our desktop app, mobile apps, reports, etc… Not to mention, third parties who just want to display Top Comments are required to have this knowledge and update their implementations as well.
Maybe we could hide what “Top Comment” means with a special endpoint:
/top_comments?filter[post_id]=123
But now clients need to know to hit this special endpoint instead of
the normal /comments
endpoint. How would they know? What if
top_comments
had special caching rules and shouldn’t be used for
this purpose?
The main problem here is there is no way to guarantee our lazy-loaded data will match our eager loaded data. Whether we fetch the Post and its Top Comments in a single request, or lazy-load that data in a separate request, the same data should always be returned.
Links solve this problem. When we fetch the Post, the top_comments
relationship will contain a URL. Clients can simply follow that URL to
lazy-load the same data. We can now change the definition of a Top
Comment - 500 upvotes, factor in recency, apply downvotes - and no
clients need to change. They simple continue to follow a Link.
1.1 Linking Relationships
When defining a relationship, we get a Link for free:
/comments?filter[post_id]=123
And when customizing a relationship with params
, our Link will be
updated:
/comments?filter[post_id]=123&filter[upvotes][gte]=100
Note: if you use the scope
block directly, it may cause incorrect
links. Avoid using scope
directly and instead use params
and
pre_load
if possible.
To manually generate a Link:
To avoid a Relationship Link altogether:
2 Resource Endpoints
To generate links, we need to associate a Resource to a URL. By default, this happens automatically:
Which would generate links to /api/v1/posts
.
2.1 Validation
Associating a Resource to an Endpoint serves two purposes. We’ve gone over link generation. But we also want to make sure we’re not linking to something that doesn’t actually exist. That’s why we perform Endpoint Validation.
If we tried to access the above resource at a /comments
endpoint:
We’d get a Graphiti::Errors::InvalidEndpoint
error. Endpoint
validation ensures that our auto-generated Links are actually valid.
To change the endpoint associated to a Resource:
Or to alter only the path:
Or to alter only the actions supported:
A resource may be accessible by multiple endpoints. Maybe PostResource
is also used at /top_posts
. We want to keep all auto-generated links
pointing to /posts
(the primary endpoint), but allow accessing
PostResource
from the /top_posts
endpoint:
3 Configuration
3.1 Autolinking
To turn off automatically generated links:
3.2 Endpoint Validation
To turn off Endpoint Validation:
3.3 Links-on-Demand
To only render links when requested in the URL with ?links=true
:
3.4 Custom Endpoint URLs
To change the URL associated with a Resource:
To generate a Relationship Link manually: