Ember Data: working with custom API endpoints

Posted by Kevin Soltysiak on October 21, 2015 Topics: ember.js and ember-data

I recently wrote about how you can use URL templates when it comes to working with nested API resources.

Today, I'll explore how you can work around another of Ember Data's shortcomings: reaching custom endpoints (eg. /posts/favorites). And once again, we'll leverage URL templates.

Before I go further: I suggest reading the article mentioned above to learn more about URL templates and the ember-cli addon we'll be using.

And once again, code samples were extracted from real-life projects, but edited for the purpose of this article, and may not work exactly as is: they're here mainly to give some substance to the concepts explored.

Fetching object(s)

Let's assume our API has two endpoints: /posts that returns all the posts there is, and /posts/favorites that returns only the favorites.

If you remember, the methods used to filled the urlSegments are given a few arguments: type, id, snapshot, and query. That last one, query, seems a good candidate for passing our custom endpoint around.

This means we will end up calling something like store.query('post', {endpoint: favorites}), which doesn't look bad. Since we're actually querying our API to return a subset of all posts, using store.query is semantically appropriate. A straightforward implementation could be the following:

If your endpoint returns only a single object instead of an array, you can use store.queryRecord and follow the same logic.

Custom actions on objects (using PUT)

Now, let's assume our API allows us to upvote and downvote posts, using PUT /post/:id/upvote and PUT /post/:id/downvote. Wouldn't it be nice to be able to call post.upvote() or post.downvote(), and get a promise as return value just as you would with post.save()?

In order to do this, we are going to hijack save()! On a persisted record, the corresponding adapter method is updateRecord, so we are going to change its URL template.

I'm assuming you will never call upvote/downvote on a record that is not yet persisted, but on a real project you should handle this scenario and prevent those actions from happening.

Let's start by defining our methods on our model. We need to store the actual endpoint somewhere, then make the call to save():

I opted to store the endpoint in a private-ish property on the model that gets nullified first-thing once the operation completes.

Then, when building our URL in the adapter, we need to check if the property exists on the snapshot and react accordingly:

And that's it. With this, we can now make domain methods and use them exactly like save() !

Custom actions on objects (using any HTTP verb)

But what if our custom actions require another HTTP verb? Since RESTAdapter.updateRecord always uses PUT, our only option here is to rewrite this method so that we can supply a custom HTTP verb.

Assuming our upvote/downvote endpoints are reached with POST instead of PUT, our solution would now look like this:

Now there is no limitations on the kind of domain methods that can be defined on our models.

While the RESTAdapter is pretty stable, you need to be careful when updating Ember Data that the reference implementation of updateRecord has not changed from the one you are specifically using, otherwise you may end up breaking things.

What did you think about this?