GraphQL and Relay on Rails — Authentication and Mutations

Gaurav Tiwari
4 min readJan 18, 2016

--

So far, we have covered basics of relay and GraphQL, building GraphQL server on top of Rails, data fetching for react components and dynamic client-side react component rendering with Relay.

In this post, we are going to cover a important requirement of any web application i.e. user authentication. Next, we will look into Relay mutations to allow our users to comment and vote on a post or comment.

Note: You can clone this branch to follow along, it has completed code for this post. You can also refer to previous posts if you are new to this series.

cd /to-your-work-directory
git clone -b blog/part4 git@github.com:gauravtiwari/relay-rails-blog.git blog
cd blog/

Authentication

To allow user authentication and authorisation lets quickly add two popular gems: Devise to and pundit into our rails application.

# Authorization
gem ‘pundit’
# Authentication
gem ‘devise’

And install the gem as per docs.

rails generate devise:install
rails generate devise User
rails g pundit:install
class ApplicationController < ActionController::Base
include Pundit
protect_from_forgery
end
Add devise to user Model

Open up user migration file generated by devise and add: name and username field as string columns and migrate the database. You can add some seed data using Faker or similar gems to test implementations (added in demo repo).

t.string :name, null: false
t.string :username, null: false, default: “”, index: true
rake db:migrate

That’s all we need for adding user authentication.

Sending cookies to GraphQL server

Since relay requests are fired separately during page load, we have to rely on cookies for authentication. Devise uses warden under the hoods so, we can use this bit of initialiser code into our app to make sure cookies are set and destroyed once a user logs in and out using warden hooks.

Warden hook to set and destroy logged in user ID

Now, we will update graphql_controller.rb to use cookies set by warden to find current_user using a before_filter hook and then use context (an arbitrary object provided by GraphQL gem) to pass current_user object to types, and will be accessible from resolve blocks inside a field or connection.

Set current user using cookies

On client, add this bit of code to components.js to ensure cookies are sent during the graphql requests to server using relay network layer.

Send cookies using relay network layer

Relay mutations

Mutations are operations that consist of writes to the data store, both on server and client followed by a fetch of any changed fields. Mutations conform to a strict API which makes it predictable to the client.

Server

Similar to types we have to define mutations to generate our Schema. Like QueryType we have used for reading the schema, we have to define MutationType as root for writing queries.

Root mutation type to define available mutation fields

Now, once we have setup root type for all our mutations. Lets define these individual mutations using a module:

app/graph/mutations/comment_mutations.rb

Comment mutation — defines edit, create and destroy field

We are defining three mutations — create, edit, destroy which are responsible for respective functions.

In each field: we are defining input fields, return fields and a resolve block for processing that mutation. For example: In create mutation we get a comment body, userID and postID as input field and then within resolve block, we find the post and then create a comment and then return the changed objects as defined in return fields.

You will notice this workflow follows a strict API:

  1. It first defines the expecting input fields (More like rails strong params)
  2. Defines the exact output to be returned using return field
  3. Then, finally a resolve to create that output

This workflow makes the response predictable, which allows faster UI updates on the client using Optimistic updates.

Lets define another mutation for vote and un-vote feature:

Note: You will notice that we accessing current_user object from the context object, which we have passed from graphql_controller.rb

Now, we are all set on server so, lets move to the client. Similar to the server, mutations on client requires separate definitions.

Client

Lets create comment mutation on client:

app/assets/javascripts/mutations/comment/create_comment_mutation.es6.js

This is a simple es6 class with couple of functions, lets take a look at them one by one:

  1. getMutation(): This basically describes what mutation will be performed. You can conditionally define mutations based on object type.
  2. getFatQuery(): A ‘fat query’ represents every field in your data model that could change as a result of this mutation. For example: In above mutation, we will add a new comment to comments collection so, as a side-effect we expect comments collection to update and also comment_count on the post component.
  3. getConfig(): Config are set of instructions on how to use the response payload from each mutation to update the client-side store. For example: In above mutation, we are defining a field change for post and range add for comments collection. This way we tell relay that we expect these changes on client.
  4. getVariables(): Remember the input fields we just defined on the server, this method basically fulfils that expectation. For example: In above mutation, we set postID and body of the comment to be passed for new comment creation.
  5. getOptimisticResponse(): As mentioned earlier, this method constructs the server response on client to faster UI updates. This optimistic response will be used to preemptively update the client cache before the server returns, giving the impression that the mutation completed instantaneously.

Lets now update our post component to use comment mutation to allow users to post comments: app/assets/javascripts/components/post.es6.js

If you have used react before the above workflow may look very similar except one major difference, we are now using relay to take care of the server and client updates instead of Ajax or Flux pattern.

Relay.Store.update(new CreateCommentMutation({post: this.props.post, body: event.target.value}))

Lets finally create mutations for voting feature:

app/assets/javascripts/mutations/vote/post_vote_mutation.es6.js

app/assets/javascripts/mutations/vote/post_unvote_mutation.es6.js

That’s all for this post. You can find a working demo and code here.

In our next post, we are going to add authorisation using Pundit to allow access control for comment and post updates. Stay tuned!

--

--