JSONAPI fields is one of the easiest ways to slim down your payloads and improve network response time. Recently, we slimmed down one payload from 90kb to 75kb by optimizing what fields and relationships were returned from a specific endpoint.
This is particularly important when a Resource has one or many relationships. If every Resource has many articles, only including the necessary article fields can improve the experience for your end-users by tens to hundreds of milliseconds.
Before applying a real-life example, let’s start with some @ember/data specifics. Currently JSONAPI fields
is not a first class citizen of @ember/data and is one area of the JSONAPI specification that is missing. Users currently must explicitly handle building the query themselves as well as potentially deciding when to reload a record or not.
Imagine you fetched a “user” record with 3 fields – name,email,address
— and subsequently, your application made a request for the same user with just name,email
. Do you need to hit network for new data? Likely the cached record is all you need.
In exploring a solution for fields
, we decided on adapterOptions
. This is a user-facing namespace for your data. You can include anything on it and it will be passed to your adapter via the snapshot. You might ask: “Why not adopt the same pattern as include
by specifying an explicit API?” Our decisions are malleable and to avoid galvanizing the options
hash passed to findRecord
, adapterOptions
seemed like the right API. This is what it would look like.
store.findRecord('post', 123, { adapterOptions: { fields: { post: 'name,body' } } });
However, as a team, we iterated on a solution that would work for all queries and found it quite difficult to support a robust answer for the community. Although grabbing fields
off of adapterOptions
and building a query was quite easy, finding a caching mechanism for findRecord
, findAll
and others was quite difficult.
Ideally, we cache on a Record’s identifier
. This is possible for findRecord
as we know the id
and type
we are fetching. As a result, we know about the identifier
before we make the request. However, for findAll
, we do not know this information before a network request goes out. Moreover, after a request is made, the data is passed through a serializer layer, losing needed context for us to cache on fields
. Future work will go into the adapter/serializer pipeline that should unlock our ability to add fields
. For now, we have a different solution.
Introducing ember-data-jsonapi-fields.
There are two basic steps to get this working. As of this post, this only works for the most common use case, findRecord
.
- Extend our base class
// adapters/application.js
import { JSONAPIFieldsAdapter } from 'ember-data-jsonapi-fields';
export default class MyJSONAPIAdapter extends JSONAPIFieldsAdapter {
...
}
- Pass
fields
into theadapterOptions
layer
store.findRecord('post', 123, {
adapterOptions: {
fields: {
post: 'name,body'
}
}
});
However, if you appreciate a svelte approach like I do, you can avoid installing this library if you decide that you don’t care about caching mechanisms (specifically shouldReloadRecord
). In this case, I would recommend simply overriding buildQuery
on your base adapter.
import JSONAPIAdapter from '@ember-data/adapter/json-api';
export default ApplicationAdapter extends JSONAPIAdapter {
buildQuery(snapshot) {
let query = this._super(...arguments);
if (snapshot.adapterOptions) {
let { fields } = snapshot.adapterOptions;
if (fields) {
query.fields = fields;
}
}
return query;
},
}
Wrapping Up
As with many decisions in the Ember community, careful thought was put forth. Our ethos to introduce and support robust solutions did not vanish here. We hope to revisit adding fields
as a first-class citizen once we re-plumb some core infrastructure.
DockYard is a digital product agency offering custom software, mobile, and web application development consulting. We provide exceptional professional services in strategy, user experience, design, and full stack engineering using Ember.js, React.js, Ruby, and Elixir. With a nationwide staff, we’ve got consultants in key markets across the United States, including San Francisco, Los Angeles, Denver, Chicago, Austin, New York, and Boston.