Last reviewed in May 2016 with Ember 2.5 Update to Ember Octane in progress!

Loading models with included relationships is straightforward with Ember Data. But saving a model along with its relationships at once (in one payload)? Not so much.

This is a common scenario – whether to simplify code, reduce roundtrips to the server or make the payload transactional.

But it seems to have no intuitive solution. The current JSON API spec does not support it. Trying to solve this can make us waste time and want to give up.

Let's explore this problem using an Invoice has many Line s example. And find a workaround!

Status quo: Saving relationships sequentially

For example:

save children to the server get children back with id s assigned set the relationship save the parent

// app/routes/application.js export default Ember . Route . extend ( { actions : { submit ( ) { const invoice = this . store . createRecord ( 'invoice' , { client : "Ricky Fort" } ) ; const lines = [ this . store . createRecord ( 'line' , { description : "Chocolate" , price : 2.10 } ) , this . store . createRecord ( 'line' , { description : "Red Bull" , price : 1.70 } ) , this . store . createRecord ( 'line' , { description : "Ball pen" , price : 4.25 } ) ] ; Ember . RSVP . all ( lines . map ( line => line . save ( ) ) . then ( ( lines ) => { invoice . set ( 'lines' , lines ) ; invoice . save ( ) ; } ) ; } } } ) ;

There is really no way of saving just the invoice and lines along with it?

// app/routes/application.js export default Ember . Route . extend ( { actions : { submit ( ) { const invoice = this . store . createRecord ( 'invoice' , { client : "Ricky Fort" } ) ; this . store . createRecord ( 'line' , { description : "Chocolate" , price : 2.10 , invoice } ) ; this . store . createRecord ( 'line' , { description : "Red Bull" , price : 1.70 , invoice } ) ; this . store . createRecord ( 'line' , { description : "Ball pen" , price : 4.25 , invoice } ) ; invoice . save ( ) ; } } } ) ;

Well… we can tell the Invoice serializer to serialize Line s…

// app/serializers/invoice.js import JSONAPISerializer from 'ember-data/serializers/json-api' ; export default JSONAPISerializer . extend ( { attrs : { lines : { serialize : true } } } ) ;

Inspecting the payload upon save gets us this promising payload: included relationships!

{ data : { type : "invoices" , attributes : { client : "Ricky Fort" } , relationships : { lines : { data : [ { id : null , type : "lines" } , { id : null , type : "lines" } , { id : null , type : "lines" } ] } } } }

Alas, not enough… where is the actual Line s data?

How do we send one request payload for all changes, all at once? Something like “deep saving” multiple models?

Ember Data's DS.EmbeddedRecordsMixin enables us to do precisely that: post embedded model relationships… but it is not compatible with JSONAPISerializer !

So the real question becomes: is there a correct way to save a model and all its relationships included in a single post payload using JSON API serialization?

Saving a model with its hasMany relationship at once

Currently not. At least not officially. Being result-oriented developers, we will try to find a workaround that we can use today.

Imagine that we made our serializer also include attributes in our relationships. That saving a new Invoice with many new Line s produced this payload:

{ data : { type : "invoices" , attributes : { client : "Ricky Fort" } , relationships : { lines : { data : [ { id : null , type : "lines" , attributes : { description : "Chocolate" , price : 2.10 } } , { id : null , type : "lines" , attributes : { description : "Red Bull" , price : 1.70 } } , { id : null , type : "lines" , attributes : { description : "Ball pen" , price : 4.25 } } ] } } } }

And that upon successful saving of all four models, our server JSON API returned the following:

{ data : { id : 1 , type : "invoices" , attributes : { client : "Ricky Fort" } , relationships : { lines : { data : [ { id : 1 , type : "lines" , attributes : { description : "Chocolate" , price : 2.10 } } , { id : 2 , type : "lines" , attributes : { description : "Red Bull" , price : 1.70 } } , { id : 3 , type : "lines" , attributes : { description : "Ball pen" , price : 4.25 } } ] } } } }

Saved models with id s assigned. Wouldn't that be awesome?!

I built an add-on:

$ ember install ember-data-save-relationships

Let's put it to use!

// app/serializers/invoice.js import JSONAPISerializer from 'ember-data/serializers/json-api' ; import SaveRelationshipsMixin from 'ember-data-save-relationships' ; export default JSONAPISerializer . extend ( SaveRelationshipsMixin , { attrs : { lines : { serialize : true } } } ) ;

And our relationships’ attributes are magically included in our payload!

Server-side handling There is only one gotcha. In order to identify outgoing/incoming models, we automatically pass a temporary internal __id__ in our embedded record attributes. Like this: Your backend API must receive and return this __id__ attribute intact. Additionally, of course, your server is responsible for: accessing relationship records and storing them appropriately

generating id s for every record

s for every record return the same structure – with updated id and attributes if applicable Here is a super basic mock server if you need one for testing purposes. No guarantees. app.post('/invoices', function(req, res) { <span class="kr">const</span> <span class="nx">id1</span> <span class="o">=</span> <span class="s2">"1internal-model"</span><span class="p">;</span> <span class="kr">const</span> <span class="nx">id2</span> <span class="o">=</span> <span class="s2">"2internal-model"</span><span class="p">;</span> <span class="kr">const</span> <span class="nx">id3</span> <span class="o">=</span> <span class="s2">"3internal-model"</span><span class="p">;</span> <span class="kr">const</span> <span class="nx">json</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">data</span><span class="o">:</span> <span class="p">{</span> <span class="nx">id</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">type</span><span class="o">:</span> <span class="s2">"invoices"</span><span class="p">,</span> <span class="nx">attributes</span><span class="o">:</span> <span class="p">{</span> <span class="nx">client</span><span class="o">:</span> <span class="s2">"Ricky Fort"</span> <span class="p">}</span><span class="p">,</span> <span class="nx">relationships</span><span class="o">:</span> <span class="p">{</span> <span class="nx">lines</span><span class="o">:</span> <span class="p">{</span> <span class="nx">data</span><span class="o">:</span> <span class="p">[</span> <span class="p">{</span> <span class="nx">id</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">type</span><span class="o">:</span> <span class="s2">"line"</span><span class="p">,</span> <span class="nx">attributes</span><span class="o">:</span> <span class="p">{</span> <span class="nx">__id__</span><span class="o">:</span> <span class="nx">id1</span><span class="p">,</span> <span class="nx">description</span><span class="o">:</span> <span class="s2">"Chocolate"</span><span class="p">,</span> <span class="nx">price</span><span class="o">:</span> <span class="mf">2.10</span> <span class="p">}</span> <span class="p">}</span><span class="p">,</span> <span class="p">{</span> <span class="nx">id</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span> <span class="nx">type</span><span class="o">:</span> <span class="s2">"line"</span><span class="p">,</span> <span class="nx">attributes</span><span class="o">:</span> <span class="p">{</span> <span class="nx">__id__</span><span class="o">:</span> <span class="nx">id2</span><span class="p">,</span> <span class="nx">description</span><span class="o">:</span> <span class="s2">"Red Bull"</span><span class="p">,</span> <span class="nx">price</span><span class="o">:</span> <span class="mf">1.70</span> <span class="p">}</span> <span class="p">}</span><span class="p">,</span> <span class="p">{</span> <span class="nx">id</span><span class="o">:</span> <span class="mi">3</span><span class="p">,</span> <span class="nx">type</span><span class="o">:</span> <span class="s2">"line"</span><span class="p">,</span> <span class="nx">attributes</span><span class="o">:</span> <span class="p">{</span> <span class="nx">__id__</span><span class="o">:</span> <span class="nx">id3</span><span class="p">,</span> <span class="nx">description</span><span class="o">:</span> <span class="s2">"Ball pen"</span><span class="p">,</span> <span class="nx">price</span><span class="o">:</span> <span class="mf">4.25</span> <span class="p">}</span> <span class="p">}</span> <span class="p">]</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="nx">json</span><span class="p">)</span><span class="p">;</span> }); app.post('/lines', function(req, res) { <span class="kr">const</span> <span class="nx">id1</span> <span class="o">=</span> <span class="s2">"0internal-model"</span><span class="p">;</span> <span class="kr">const</span> <span class="nx">json</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">data</span><span class="o">:</span> <span class="p">{</span> <span class="nx">id</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">type</span><span class="o">:</span> <span class="s2">"lines"</span><span class="p">,</span> <span class="nx">attributes</span><span class="o">:</span> <span class="p">{</span> <span class="nx">client</span><span class="o">:</span> <span class="s2">"Ricky Fort"</span> <span class="p">}</span><span class="p">,</span> <span class="nx">relationships</span><span class="o">:</span> <span class="p">{</span> <span class="nx">invoices</span><span class="o">:</span> <span class="p">{</span> <span class="nx">data</span><span class="o">:</span> <span class="p">{</span> <span class="nx">id</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">type</span><span class="o">:</span> <span class="s2">"invoice"</span><span class="p">,</span> <span class="nx">attributes</span><span class="o">:</span> <span class="p">{</span> <span class="nx">__id__</span><span class="o">:</span> <span class="nx">id1</span><span class="p">,</span> <span class="nx">client</span><span class="o">:</span> <span class="s2">"Ricky Fort"</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="nx">json</span><span class="p">)</span><span class="p">;</span> }); }

So now we can save() like this, and it works!

// app/routes/application.js export default Ember . Route . extend ( { actions : { submit ( ) { const invoice = this . store . createRecord ( 'invoice' , { client : "Ricky Fort" } ) ; this . store . createRecord ( 'line' , { description : "Chocolate" , price : 2.10 , invoice } ) ; this . store . createRecord ( 'line' , { description : "Red Bull" , price : 1.70 , invoice } ) ; this . store . createRecord ( 'line' , { description : "Ball pen" , price : 4.25 , invoice } ) ; invoice . save ( ) ; } } } ) ;

Check out the inspector:

Saving a belongsTo relationship

Alternatively, we could do:

// app/routes/application.js export default Ember . Route . extend ( { actions : { submit ( ) { const invoice = this . store . createRecord ( 'invoice' , { client : "Ricky Fort" } ) ; const line = this . store . createRecord ( 'line' , { description : "Chocolate" , price : 2.10 , invoice } ) ; line . save ( ) ; } } } ) ;

We must remember to activate serialization (and include the mixin) in the appropriate serializer:

// app/serializers/line.js import JSONAPISerializer from 'ember-data/serializers/json-api' ; import SaveRelationshipsMixin from 'ember-data-save-relationships' ; export default JSONAPISerializer . extend ( SaveRelationshipsMixin , { attrs : { invoice : { serialize : true } } } ) ;

I for one would love to see how the final JSON API 1.1 specification winds up!

Does this help? What do you think? Looking forward to your feedback or any issues you may encounter!