I got really excited in January when GraphQL and Relay were introduced during the React conference. In the beginning of July I went to the React Europe conference. I wanted to learn more about GraphQL although I didn’t expect that GraphQL would be announced there. But I was wrong and never been happier to be wrong. So when I got back to London I sat down to refactor my project and move it to GraphQL.
A pet project. Or something like that
My last contract ended at the end of March and since then I’ve been working on this project full time.
Architecture at the beginning
My last contract was at Red Badger. I started to learn React there. They started a React meetup last year which got really popular in a very short time. At the meetup in last December Stuart Harris talked about building isomorphic React SPAs. In that talked he showed a sample architecture they came up with:
I really liked the idea so borrowed some ideas (ok, almost everything) of it to my own project. The only change I made was that I used react-router instead of monorouter. So in the beginning the stack I used was this:
- React for views
- Immstruct for cursor over immutable data
- React router for routing
- ExpressJs for the web framework
- Bookshelf for ORM with Postgresql database
- Custom renderer that works on server and client side as well
This setup worked well. And to be honest there wasn’t any really good reason why I should have changed it. But I did.
Change is good
Changes in architecture
As GraphQL was announced during the conference and I had the freedom to spend a few days or more and refactor my application. Just before I started I had read an article: Elegant Functional Architecture for React by Gil Birman. And it made me think about using Immstruct and at the end I decided to drop it in favour of the subedit function.
I will use the post page as an example from now on. A post is an entry in a journal and it’s built up from blocks (eg image, text, route, embed). Users can comment on a post or like it
First we need to create the schema. I wrote a script that generates the types without the resolve function implementation and schema from a database schema. After running that we have the following schema:
Next step is implementing the resolve functions. As I’m using Bookshelf, it’s a pretty simple. I need to import the model, add an argument to the post, so it knows which post is requested:
So now we can request a post (after we create a route in express) with the following request:
With this implementation we have a response that not only includes the response of a call to the /post/:id REST endpoint but saves a lot of roundtrip to the other endpoints as well (or it’s equivalent to a custom endpoint call).
This is very simple so far, but what about routing when the id cannot be set directly, and a post should be looked up by the slug of the journal and post. And what if a post is private and can be accessed by the author only?
There is no recommendation how to implement authorisation in GraphQL (or I haven’t found any). In my case I need to lookup the user object from the current session and pass it to the post field resolve function.
To achieve this we can wrap the original QueryType in another query type, let’s call it SessionQueryType. This session query will be the root query. It has only one field at the moment: data. This field has one argument, the id of the logged in user. The loggedInUserId parameter is set in the function that is invoked from the /graphql route. In the data field resolve function a user object can be looked up from the database by the id. If the id is null, then the lookup will return null as well. After that we can pass the resolved user object to the post resolve function. The updated implementation of that function now has two arguments: the first one is the resolved user and the second one is an object that includes the defined arguments: post id, journal slug and post slug. With these parameters and an authorise function implementation on the Post model, we can decide if we grant or deny access to the post.
Retrieving data on the client
So far we have a working endpoint from which we can retrieve all the data that is needed to show the post page. But how to get the data on the client?
We need a renderer function and a component to render. The renderer function is responsible to retrieve the data for a route (let’s say this is the route for a post: /:journal/:post). React router identifies what component should be rendered for a route, and it provides that component to the renderer function. The component must describe what data it needs.
Post component is the one that will be rendered for the post route. It (and every main component) has only one query, called main. This function will be invoked from the renderer and it returns the GraphQL query string. One solution is to write the whole query in the main component. The other one that I prefer more is based on Relay concept: every component describes their data needs so a parent component can collect the whole data tree description from its children. So every component has a static queries object, that can contain multiple functions and each of them returns a GraphQL query string. The getQuery method is added to all components. The function needs to get the name of the query, and can get multiple parameters which will be passed to that function.
Now we have a component so we need to be able to render the page. And it’s important that a page can be rendered on server and client side as well. As we want to use immutable-js, and have one central state in the application this renderer implementation should handle the app rerendering every time the app state is changed.
When the Renderer class is instantiated it gets the service implementation as a parameter. On the server it’s the graphqlService itself, on the client it fetches the data from the GraphQL endpoint via AJAX. After server side rendering it sends the data to the client with the HMTL so on first render the page doesn’t need to fetch it again. On the client side we need to call the attach method of the instance so we can set the starting state and the DOM node to which it should render the app.
When the render function runs it gets the graphql query through the main component. If the current route has parameters it replaces those parameters in the query string (see Post.jsx:38 and renderer.js:60). In our case the route has two parameters: journal and post as these are required to get the post data from the database.
Updating the view
The Renderer class has a very important function: the edit. This with the subedit function is how the page data can be modified. There is a detailed explanation how it works in the article I mentioned above.
At this point we can render pages either on server or client side. The last missing piece is updating an entry in the database.
Let’s implement the like button on the post page. First we have to create a mutation query and include it in the schema.
With this schema a like object can be added to or removed from a post. It follows the same idea as in the querying: the original graphql request is wrapped in to a SessionQueryType so the user who talks to the endpoint can be passed to the final resolve function.
On the client side we need to implement the like button behaviour and add the GraphQL requests.
There are two new methods in the CommentsAndLikes component static queries object: like and unlike. Both functions have one parameter, the id of the post. When a user clicks on the like button, the handler retrieves the query and sends it to the endpoint. If the request was successful it adds the new like to the list (or remove a like if it was an unlike request) and since the main state was modified the page gets rerendered.
So finally we have a page that shows a post and users can like it. The page can be rendered either on server or client side. Each component describes their data need, and during rendering it is retrieved from a GraphQL endpoint. We can deny access to a post based on the current user session.
It was really simple and straightforward to move the current REST api to GraphQL. And fun. The bigger task was to refactor the UI components. Although it wasn’t necessary since I could have set the GraphQL query in the main component. But adding data description to each component helped a lot to break down bigger components in to smaller ones and reuse those in multiple places. So there is less repetition in the view implementation.
Dropping Immstruct was a good decision as well. With Immstruct the shouldComponentUpdate function needs to perform a deep comparison on the props. And with a more complex data structure it can be slow on mobile (sometimes typing in to a textarea could have resulted missing letters on a Nexus 5, and now there is no difference between mobile and desktop browsers).
I’m really looking forward to know more about Relay, since there’s lot more in it than just fetching the data. But right now I’m pretty happy with my current solution. And probably I keep it for a few months. Mainly because I start to work on a native app using React Native. And hopefully I can reuse these ideas there as well.