Making Requests
Requests are how your application fetches or updates data stored remotely.
What Does Remote Mean?
Most commonly remote data refers to data that is stored on your server and accessed and updated via your backend API.
But it doesn't have to be! Remote really boils down to persistence - the ability for data to be reliably stored and retrieved again at a later time.


Request Options
WarpDrive uses the native Fetch interface for Request and as the foundation upon which requests are made. This ensures that if the platform supports it, WarpDrive exposes it: platform APIs are never hidden away.
const { content } = await store.request({
url: '/api/users'
});
const { content } = await store.request({
url: '/api/users',
method: 'POST',
headers: new Headers({
'Accept': 'application/json',
'Content-Type': 'application/json',
}),
body: JSON.stringify({
filter: {
$in: { teams: ['1', '9', '42'] }
},
search: { name: 'chris' }
})
});
const { content } = await store.request({
url: '/actions/like',
method: 'POST',
headers: new Headers({
'Accept': 'application/json',
'Content-Type': 'application/json',
}),
body: JSON.stringify({
actor: { type: 'user', id: '1' },
content: { type: 'comment', id: '42' }
})
});
Of course, writing requests so manually quickly gets repetitive.
WarpDrive offers two abstractions for helping to write organized, reusable requests.
- Builders - simple functions that produce a json request object
- Handlers - middleware that enable enhancing, modifying, or responding to requests
Here's an example of how the requests above could be expressed as builders:
function getUsers() {
return {
url: '/api/users'
}
}
const { content } = await store.request(getUsers());
function queryUsers(query) {
return {
url: '/api/users',
method: 'POST',
headers: new Headers({
'Accept': 'application/json',
'Content-Type': 'application/json',
}),
body: JSON.stringify(query)
}
}
const { content } = await store.request(
queryUsers({
filter: {
$in: { teams: ['1', '9', '42'] }
},
search: { name: 'chris' }
})
)
function createContentLike(actor, content) {
return {
url: '/actions/like',
method: 'POST',
headers: new Headers({
'Accept': 'application/json',
'Content-Type': 'application/json',
}),
body: JSON.stringify({
actor,
content
})
}
}
const { content } = await store.request(createContentLike({
actor: { type: 'user', id: '1' },
content: { type: 'comment', id: '42' }
}));
Builders make it easy to quickly write shareable, reusable requests with typed responses that mirror your application's capabilities and critical business logic.
Requests Do Not Need To Use Fetch
The native Request interface provides a convenient, feature-rich way to describe the data you want to retrieve or update – but ultimately request handlers get to decide how that occurs.
Request handlers can be used to connect to any data source via any mechanism. Besides fetch, this might be localStorage, XMLHttpRequest, WebSockets, ServerEvents, MessageChannel, or something else entirely!
import Store from '@ember-data/store';
import RequestManager from '@ember-data/request';
import Fetch from '@ember-data/request/fetch';
import {
SessionSettingsHandler,
FileSystemHandler
} from './app/handlers';
export default class AppStore extends Store {
requestManager = new RequestManager()
.use([
SessionSettingsHandler,
FileSystemHandler,
Fetch
]);
}
Requests Aren't Just For APIs
Requests are a manner of expressing what data you want to use or an update to data you want to make.
The File System, browser managed storage mediums such as IndexedDB and localStorage, or WebAssembly builds of sqlite3 are all common examples of persistent or remote data sources that aren't accessed via connecting to a server.
The Chain of Responsibility
When we configured the RequestManager
above, you may have noticed that when we gave it an array of handlers with which to respond to requests.
RequestManager
follows the chain-of-responsibility pattern: each handler in our array may choose to respond to the request, modify it, or pass it along unchanged to the next handler in the array, in array order.

