Turbine

Relay-like REST-friendly Immutable-based React data library

TL;DR https://github.com/chute/turbine

Note 1: This is a more narrative version of a presentation I made at #ReactEurope (cats are omitted). Note 2: There’s a follow up post GraphQL in the age of REST APIs where I discuss a concept for making GraphQL work with REST endpoints.

There are many reasons why we all love React so much.

I think the most important one is that it is declarative.

You only need to implement components that get data as the input, and produce UI as the output. Ideally, that’s all you’d need to do, because that’s what makes your application unique.

Unfortunately, we also need to manage the data.

In the first wave of Flux frameworks we built our own (just like everyone else). Flux is a great pattern but we found that practically everything we were doing was fetching remote data or updating them, and for each we had to write a lot of the same (imperative) code.

And then Relay was announced. Relay takes the declarative concept one step further. It allows you to declare the query, and takes care of managing the data for you.

Turbine has the same concept but it integrates declarative queries with REST APIs.

The case for REST

Relay works with a GraphQL server. Our entire platform is built as a collection of REST services, and we have JS SDKs that wrap around the endpoints. Moreover, 3rd party services also expose REST APIs and provide their own SDKs.

Let’s consider an example. Asset describes a photo/video, and can belong to an album.

On the server, AssetsController handles request on the Asset resource.

In the SDK, the Asset.js model provides a set of methods that wrap the API endpoints.

Asset.js may look like this:

export default class Asset extends new Immutable.Record({

// define attributes with default values

id: null,

url: ''

}) {

// define methods

static query(options) {

const { album_id, ...params } = options return new Promise((resolve, reject) => {

request(`/albums/${album_id}/assets`, params).then(data => {

const assets = data.map(item => new Asset(item))

resolve(assets)

)

})

}

// ...

}

We’re using Immutable Record but you could use any class to implement the model. The only requirement is that the model defines the query, find, create, update and delete methods which all return a promise. You can also define additional helper methods that will be available on each instance.

People using the SDK can call Asset.query to fetch the list of assets.

Asset.query({album_id: 123, sort: ‘likes’}).then(assets => …)

Wrapping a React component

When you want to render a list of assets in React, you’d create an AssetsView component:

import React, { PropTypes } from ‘react’ const AssetsView = React.createClass({

propTypes: {

assets: PropTypes.array.isRequired

}, render() {

// …

}

}) export default AssetsView

AssetsView requires a list of assets. To render it, you’d use:

<AssetsView assets={[…]} />

How you get the list of assets, that’s your problem and responsibility.

Now let’s see how that changes with Turbine.

import React, { PropTypes } from 'react'

import Turbine, { graphql } from 'turbine'



const AssetsView = React.createClass({

propTypes: {

assets: PropTypes.array.isRequired

},



render() {

// ...

}

})



export default Turbine.container(AssetsView, {

queries: {

assets: graphql`

{

assets(album_id: <album_id>) {

id,

url

}

}

`

}

})

Instead of exporting the component directly, we wrap it in Turbine container, and specify the query as the second parameter. The format of the query is the very same GraphQL as Relay uses.

To render AssetsView, give it album_id as a prop.

<AssetsView album_id={123} />

Turbine will call Asset.query, fetch the assets, cache them in Turbine Store, and then render AssetsView.

Comparison with Relay

There’s a lot more in Relay than fetching data.

Obviously one great benefit of Relay is combining all the data you need into one request. That’s not really possible if you’re splitting the query into individual models already on the client.

On the other hand, because Turbine doesn’t need a server it may be easier for you to get started, and it could even be enough for your application.

With that said, Turbine is still an experiment. At this point it is a separate implementation since Relay is not open sourced yet (will be in August) but it’s designed to be compatible with Relay on the component level.

Additionally, there are plans to integrate Turbine as a data layer for Relay (see the next blog post).

In any case, if you want to get a feel for declarative data fetching, you’re welcome to try Turbine out. Any feedback appreciated!

https://github.com/chute/turbine