This is a series blog post cover above three topics of GraphQL:
- About GraphQL
- Building a basic API with Rails
- Some best practices
GraphQL is not a new thing, it already exist for a while, the reason I decide to investigate it is because Github released their GraphQL API alpha currently. We know Github has been a very good company for a long time, their RESTful API also becomes some kind of standard for developers, so it’s pretty interesting to see what’s new they’ve been released.
This topic is based on my MeetUp talk, to know more you can check my slide and example repo.
Alright, Let’s get started!
Say we have 2 types shares many fields, for example they all have such like id
, updated_at
, created_at
basic ActiveRecord fields. How do we clean those types? One way to do it is use Interfaces. Let’s take a look:
Before refactoring, UserType
and PostType
looks 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 a active_record_interfaces.rb
file and defines id
, updated_at
, 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
Then our UserType
and PostType
can becomes 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
We use resolve
block to handle each fields’ behaviour, however it will becomes fragile if we add code logic into resolve
block, and also it’s not easy to test. So the better way is to keep logic away from resolve
block but stay at Service Object (or other patterns you want to use, the idea is abstract it).
Ex:
The code below
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
So that in this case we only need to test Graph::CreatePostService
, more testable and maintainable.
In graphql-ruby gem, there are couple built in module with Relay
namespace, if you want your API looks like GitHub GraphQL API or just want to integrate it with Relay, it will saves you tons of time.
Check those modules below to get a taste:
- GraphQL::Relay::ConnectionType
- GraphQL::Relay::Node
- GraphQL::Relay::Edge
Those are the experience I learned, have more tips to share? Leave a comment here. Thanks!