Skip to content

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.


waves of reactive signals light up spacewaves of reactive signals light up space

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.

ts
const { content } = await store.request({
  url: '/api/users'
});
ts
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' }
  })
});
ts
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:

ts
function getUsers() {
  return {
    url: '/api/users'
  }
}

const { content } = await store.request(getUsers());
ts
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' }
  })
)
ts

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!

ts
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.

WebSockets, ServerEvents

WebSockets, ServerEvents,

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.

a flow diagram showing data resolving from server via a chain of request handlersa flow diagram showing data resolving from server via a chain of request handlers

Released under the MIT License.