Building a GraphQL API in Rails — Part 3: Best Practice

Draw & Code
3 min readApr 7, 2017

--

This is the final post in a series covering three topics relating to GraphQL:

  1. What is GraphQL?
  2. Building a basic API with Rails
  3. Some best practices

As I mentioned previously, I decided to write this series as Github recently released its GraphQL API alpha. Github has been at the forefront of API-driven development for some time now, and its RESTful API is an exemplary API design referenced by many developers. So it’s always pretty interesting to explore new Github releases.

This post is based on my talk at RoRoSyd Meetup. If you’d like to know more, you can check out my slides and an example repo.

Alright, let’s get started on part 3!

1. Interfaces

Say we have 2 types that share many fields, like id, updated_at, and created_at basic ActiveRecord fields, for example. How do we clean these types? One way to do it is by using interfaces.

Let’s take a look.

Before refactoring, UserType and PostType look like this:

# app/graph/types/user_type.rb
UserType = GraphQL::ObjectType.define do
name "User"
description "A user"

field :id, types.Int
field :email, types.String
field :updated_at do
type types.Int

resolve -> (obj, args, ctx) {
obj.updated_at.to_i
}
end
field :created_at do
type types.Int

resolve -> (obj, args, ctx) {
obj.created_at.to_i
}
end
end
# app/graph/types/post_type.rb
PostType = GraphQL::ObjectType.define do
name "Post"
description "A post"

field :id, types.Int
field :title, types.String
field :content, types.String
field :updated_at do
type types.Int

resolve -> (obj, args, ctx) {
obj.updated_at.to_i
}
end
field :created_at do
type types.Int

resolve -> (obj, args, ctx) {
obj.created_at.to_i
}
end
end

Now let’s create an active_record_interfaces.rb file and define id, updated_at, and created_at.

# app/graph/types/active_record_interfaces.rb

ActiveRecordInterface = GraphQL::InterfaceType.define do
name "ActiveRecord"
description "Active Record Interface"

field :id, types.Int
field :updated_at do
type types.Int

resolve -> (obj, args, ctx) {
obj.updated_at.to_i
}
end
field :created_at do
type types.Int

resolve -> (obj, args, ctx) {
obj.created_at.to_i
}
end
end

In doing so, our UserType and PostType become much clearer:

# app/graph/types/user_type.rb

UserType = GraphQL::ObjectType.define do
interfaces [ActiveRecordInterface]
name "User"
description "A user"

field :email, types.String
end
# app/graph/types/post_type.rb

PostType = GraphQL::ObjectType.define do
interfaces [ActiveRecordInterface]
name "Post"
description "A post"

field :title, types.String
field :content, types.String
end

2. Service objects

We use resolve blocks to handle each field’s behaviour. However if we add code logic into a resolve block, it becomes fragile, and it’s also not easy to test. That’s why control flow logic should be abstracted to a Service Object and not placed in a resolve block.

Here’s an example.

This code:

CreatePostMutation = GraphQL::Relay::Mutation.define do
# ...

resolve -> (object, inputs, ctx) {
post = ctx[:current_user].posts.create(title: inputs[:title], content: inputs[:content])

{
post: post
}
}
end

Could be:

CreatePostMutation = GraphQL::Relay::Mutation.define do
# ...

resolve -> (object, inputs, ctx) {
Graph::CreatePostService.new(inputs, ctx).perform!
}
end

In the second case, we only need to test Graph::CreatePostService. It’s more testable and easier to maintain.

3. Use the Relay module

In graphql-ruby gem, there are a couple of built in modules with the Relay namespace. If you want your API to look like GitHub GraphQL API, or you just want to integrate it with Relay, it will save you tons of time.

Check out the modules below to get a taste:

  • GraphQL::Relay::ConnectionType
  • GraphQL::Relay::Node
  • GraphQL::Relay::Edge

These are all my experiences, and things I’ve learned. Do you have any more tips to share? Leave a comment here. Thanks!

By Wayne Chu

--

--

Draw & Code

Draw & Code is where geekery manifests itself at fintech startup Maxwell Forest. Here you will find tips, tricks and tales from our engineers and designers.