GraphQL in Production

At Make School, we are using GraphQL and a Relay-like interface to power our Summer Academy. Our students have a SPA interface that they can use to browse the curriculum, watch recorded lectures, and ask questions.

As GraphQL is relatively new and there are lots of questions about it, I thought it would be beneficial to show a subset of our own schema and how we implement a few features on top of it.


Schema

Here’s a subset of our schema that we’ll use to implement a few features. We have about six other types and seven additional mutation types not shown here, which span curriculum content/progress, user profile management, editing questions / selecting best answers, etc.

interface IUser {
name: String,
profile_image_url: String,
checked_in: Boolean
}
type User : IUser {
name: String,
profile_image_url: String,
checked_in: Boolean
}
type Viewer : IUser {
name: String,
profile_image_url: String,
current_track: Track,
accessible_tracks: [Track],
}
type Track {
id: String,
name: String,
image_url: String,
users: [IUser],
}
type QueryRoot {
viewer: Viewer
}
type MutationRoot {
set_profile_picture(url: String),
change_track(id: String)
}

Feature: Static Navigation Bar

We have a NavBar component on most pages that shows the user some information about their current track and user profile.

This is implemented in React using a Relay-like interface to specify all of our data requirements via GraphQL:

var NavBar = React.createClass({
// …
});
var NavBarContainer = Delay.createContainer(NavBar, {
queries: {
viewer: graphql`
{
viewer {
current_track {
name,
image_url,
},
name,
profile_image_url
}
}
`
}
});

The Relay-like API ensures that our NavBar component is rendered once all of the data is retrieved. The data itself is passed in via props.

But what if our user is visiting the page for the first time, and they don’t have a track or profile picture?

This seemed like a good place to insert our “first time user” flow. Inside NavBar, we have an invariant that looks like:

if (!this.props.viewer.current_track || 
!this.props.viewer.profile_image_url) {
this.transitionTo(‘/welcome’);
}

Feature: Welcome Page

We have another component that represents our Welcome page. Using the same Relay-like API, that looks like:

var WelcomePageContainer = Delay.createContainer(WelcomePage, {
queries: {
viewer: graphql`
{
viewer {
accessible_tracks {
id,
name,
image_url,
}
}
}
`
}
});

We use the accessible_tracks to render a grid that allows the user to select a track. Once selected, we fire off a change_track mutation, passing in the ID.

We do the same thing with the profile picture. Our front-end has a picture selection UI and produces a URL, which is passed on to set_profile_picture.

Once we’re done, we can redirect back to the previous page.

Bonus: Authentication

There’s a little bit of confusion regarding user authentication with GraphQL and Relay. I’m sure we’ll hear more details from Facebook about this in the future, but in the meantime, I’ll explain our approach:

Cookies!

Our login flow is separate from our SPA. If the user isn’t authenticated from the Rails end, they are redirected as appropriate. Once the SPA is rendered, they must be logged in, and therefore all AJAX calls going across the wire already have all of the necessary cookies. We pull out the cookie on the other end to authenticate the user.

There are no other changes to the server necessary for verification, beyond what you would already be doing in a model or controller. We’re using graphql-ruby, so our change_track mutation might look something like this:

field.resolve -> (obj, args, ctx) do
if ctx[:user].has_access_to?(args['track'])
ctx[:user].change_track(args['track'])
end
end