- Introduction
- URL Names
- ID's
- GET [ Multiple Records ] [ Single Record ] [ Querying Multiple Records ] [ Querying a Single Record ]
- POST
- PUT
- DELETE
- Model Relationships [ Side-loaded Without Query ] [ Embedded Data With Query ] [ Embedded Data Without Query ] [ Async Loading ]1. Dates
- Error Formatting
ASH adheres to REST standards and uses Ember's RESTAdapter. The following is a combination of REST and Ember-specific guidelines to help facilitate API development at ASH. Adhering to these guidelines will allow for the simplest and most painless use of the Ember Data library. Much of this was adopted from Ember Data's API documentation, so for more reading, check the Ember Data documentation.However, Ember Data is not the only reason behind this structure, it helps to create a consistent API architecture making it easier to plug into other platforms and frameworks (e.g., backend, native apps, etc.).
You will need to ensure that you follow the url structure, object structure, and status code. If not, the team will need to make sure adapters and serializers are set up to compensate for this in Ember.
URLs should be the same for GET, POST, PUT, and DELETE. For instance, api/users not api/getUsers or api/deleteUser. In the case where the client is requesting or modifying an existing record, the id should be passed after users. For instance: api/users/324 should be able to accept GET, DELETE, or PUT requests.
In general, each record needs to have an id. So the API should supply one, even if it's not the real ID that is stored in the database.
4.1: GET all records
URL
: apiHost.com/movies
Request Method
: GET
Ember Data Method
: findAll('movie')
HTTP Status : 200
Payload :
{
"movies": [
{
"id": 1,
"title": "Raging Bull",
"year": "1980"
},
{
"id": 2,
"title": "Goodfellas",
"year": "1990"
}
]
}Payload (If no data is found, then an empty array is returned) :
{
"movies": []
}4.2: GET a single record
URL
: apiHost.com/movies/2
Request Method
: GET
Ember Data Method
: findRecord('movie', 2)
HTTP Status : 200
Payload :
{
"movie": {
"id": 2,
"title": "Goodfellas",
"year": "1990"
}
}HTTP Status : 404
Payload :
Content should be an error and may differ, as error style is defined by the server.
4.3: GET multiple records using a query
To get multiple records based on parameter criteria.
URL
: apiHost.com/movies?year=1990
Request Method
: GET
Ember Data method
: query('movie', { year: '1990' })
HTTP Status : 200
Payload :
{
"movies": [{
"id": 2,
"title": "Goodfellas",
"year": "1990"
},
{
"id": 5,
"title": "Red Riding Hood",
"year": "1990"
}]
}Payload (If no data is found, then an empty array is returned) :
{
"movies": []
}4.4: GET a single record using a query
To get a single record based on parameter criteria when the result is known to be one record.
URL
: apiHost.com/movies?title=Goodfellas
Request Method
: GET
Ember Data method
: queryRecord('movie', { title: 'Goodfellas' })
HTTP Status : 200
Payload :
{
"movie": {
"id": 2,
"title": "Goodfellas",
"year": "1990"
}
}Payload (If no data is found, then an empty array is returned)
:
{
"movie": {}
}URL
: apiHost.com/movies
Request Method
: POST
Ember Data Method :
//create movie3 record in local store
let movie3 = get(this, 'store').createRecord('movie', {
title: "Crimson Tide",
year: "1995"
});
//persist movie3 via POST request to apiHost.com/movies
movie3.save();Payload :
{
"title": "Crimson Tide",
"year": "1995"
}HTTP Status : 201
Payload :
{
"movie": {
"id": 3,
"title": "Crimson Tide",
"year": "1995"
}
}
PUTrequests update records that already exist with new or updated information
URL
: apiHost.com/movies/2
Request Method
: PUT
Ember Data Method :
//lookup record in the local store
let movie = get(this, 'store').findRecord('movie', 2); // returns record of {"id": 2, "title": "Goodfellas", "year": "1990"}
movie.set('title', 'Goodfellers'); //update an existing property
movie.set('radioheadOnSoundtrack', false); //add a new property
// set method only updates the record in the local store without making a network request yet.
movie.save(); //save() initiates a PUT request to apiHost.com/movies/2Payload :
{
"title": "Goodfellers",
"year": "1990",
"radioheadOnSoundtrack": false
}HTTP Status : 200
Payload :
{
"movie": {
"id": 2,
"title": "Goodfellers", //title has been updated
"year": "1990",
"radioheadOnSoundtrack": false //property has been added
}
}While you can add a new property in the PUT request, it's not good practice, since your app should be working off a schema rather than arbitrarily adding properties.
The api can also return a 204 with an empty payload, but this is not preferred. It's preferred to use a 200 so the API can compute or serialize any data and send back to the front end.
URL
: apiHost.com/movies/2
Request Method
: DELETE
Ember Data Methods:
Examples are assuming you have already set movie to a record in your store using findRecord('movie', 2) or a similar method
Deletes Only (you must save to persist)
movie.deleteRecord(); //Deletes it from the local store, but no network request to the API yet
movie.save() //DELETE network request to apiHost.com/movies/2
Deletes and Persists
movie.destroyRecord(); //Deletes it from the local store and sends a DELETE network request to apiHost.com/movies/2
HTTP Status : 204
Payload : Empty (No Content)
The Ember App Expects a 204 with No Content because, is terminated by the first empty line after the header fields because it cannot contain a message body.
Use this method when you can safely assume that you generally want the list of actors when the
moviesendpoint is accessed
URL: api.com/movies
Payload:
{
"movies": [
{
"id": 1,
"title": "Raging Bull",
"year": "1980",
"actors": [1,2,3]
},
{
"id": 2,
"title": "Goodfellas",
"year": "1990",
"actors": [1,2,4]
},
{
"id": 4,
"title": "Cape Fear",
"year": "1991",
"actors": [1,5,6]
}
],
"actors":[
{
"id":1,
"name": "Robert De Niro"
},{
"id":2,
"name": "Joe Pesci"
},{
"id":3,
"name": "Cathy Moriarty"
},{
"id":4,
"name": "Ray Liotta"
},{
"id":5,
"name": "Nick Nolte"
},{
"id":6,
"name": "Illeana Douaglas"
}
]
}Alternatively, you can GET api.com/movies/1 and the api will only return Raging Bull and its actors. It will not return all actors for all movies.
Use this method when you can safely assume that you generally want the list of actors when the
moviesendpoint is accessed. If you have a lot of shared actors, this may result in a significantly larger payload.
Notice that the payload is larger here becuase shared actors (De Niro and Pesci) are repeated, whereas they are not in the side-loaded example.
URL: api.com/movies
Payload:
{
"movies": [
{
"id": 1,
"title": "Raging Bull",
"year": "1980",
"actors":[
{
"id":1,
"name": "Robert De Niro"
},{
"id":2,
"name": "Joe Pesci"
},{
"id":3,
"name": "Cathy Moriarty"
}
]
},
{
"id": 2,
"title": "Goodfellas",
"year": "1990",
"actors":[
{
"id":1,
"name": "Robert De Niro"
},{
"id":2,
"name": "Joe Pesci"
},{
"id":4,
"name": "Ray Liotta"
}
]
},
{
"id": 4,
"title": "Cape Fear",
"year": "1991",
"actors":[
{
"id":1,
"name": "Robert De Niro"
},{
"id":5,
"name": "Nick Nolte"
},{
"id":6,
"name": "Illeana Douaglas"
}
]
}
]
}Alternatively, you can GET api.com/movies/1 and the api will only return Raging Bull and its actors.
Use this method when you want the ability to toggle including actors when the
moviesendpoint is accessed
URL: api.com/movies/?include=actors
Payload:
{
"movies": [
{
"id": 1,
"title": "Raging Bull",
"year": "1980",
"actors":[
{
"id":1,
"name": "Robert De Niro"
},{
"id":2,
"name": "Joe Pesci"
},{
"id":3,
"name": "Cathy Moriarty"
}
]
},
{
"id": 2,
"title": "Goodfellas",
"year": "1990",
"actors":[
{
"id":1,
"name": "Robert De Niro"
},{
"id":2,
"name": "Joe Pesci"
},{
"id":4,
"name": "Ray Liotta"
}
]
},
{
"id": 4,
"title": "Cape Fear",
"year": "1991",
"actors":[
{
"id":1,
"name": "Robert De Niro"
},{
"id":5,
"name": "Nick Nolte"
},{
"id":6,
"name": "Illeana Douaglas"
}
]
}
]
}Alternatively, you can GET api.com/movies/1/?include=actors and the api will only return Raging Bull and its actors.
URL: api.com/movies
Payload:
{
"movies": [
{
"id": 1,
"title": "Raging Bull",
"year": "1980",
"actors":[1, 2, 3]
},
{
"id": 2,
"title": "Goodfellas",
"year": "1990",
"actors":[1, 2, 4]
},
{
"id": 4,
"title": "Cape Fear",
"year": "1991",
"actors":[1, 5, 6]
}
]
}The JavaScript can then make individual requests to api.com/actors/1, api.com/actors/2, api.com/actors/3, etc. because those id's were referenced in the original payload.
DateTime properties should use the ISO 8601 format below.
//No matter where the user is, they will see the time as 3:26pm
//local to their timezone
var local ='2017-05-10T15:26';
//The Z at the end means that this is 3:26 UTC time. Depending
//on how this date is implimented client-side, the user will
//see their local conversion. For instance, 11:26am EST during
//daylight saving time or 10:26 after daylight saving time ends.
var utc = '2017-05-10T15:26Z';
//In this case, the datetime is 3:26 EST, so it will convert
//to 12:26 PST if the user is in San Diego.
var offset = '2017-05-10T15:26-0400'
//In this case, the datetime is 2:26 EST because daylight saving
//time has ended (the month was changed to December), so it will
//convert to 11:26 PST if the user is in San Diego.
var offsetWithoutDaylightSaving = '2017-12-10T15:26-0400'It's important to remember that UTC is different in the USA any given date, depending on if we are in the middle of daylight saving or not.
If the API passes a date without a time, it will be converted to that date at midnight UTC.
var newYearsDay = '2017-01-01';
new Date(newYearsDay); //Sat Dec 31 2016 16:00:00 GMT-0800 (Pacific Standard Time)Because PST is 8 hours behind UTC, the date will display 12/31/2016.
For any error, the server should respond with the correct status code as well as a message in the response body.
If a response is considered a failure, the JSON payload is expected to include
a top-level key errors, detailing any specific issues. If the JSON payload does not
include a top level errors key, then we will need to munge the data.
// Somewhere in Ember-land, attempting to save a model.
// Omitted for brevity...
actions: {
save(model) {
model.save().then(() => {
// For successful save
}).catch((error) => {
// For unsuccessful save
// Model had server errors for attributes: `firstName` and `email`
});
}
}// Ideal JSON error payload
{
"errors": [
{
"detail": "First name is required",
"source": {
"pointer": "data/attributes/first-name"
}
},
{
"detail": "Email is required",
"source": {
"pointer": "data/attributes/email"
}
}
]
}Thanks to Ember Data, we have access to our errors via our model.
// Somewhere in Ember-land...
// Omitted for brevity...
Ember.get(this, 'model.errors.first-name')
// returns a `first-name` error object!
// => { "attribute": "firstName", "message": "First name is required" }
Ember.get(this, 'model.errors.email')
// returns an `email` error object!
// => { "attribute": "email", "message": "Email is required" }Or, you can render them in a template!