GraphQL with Ruby on Rails and Nuxt.js— Part 1
Besides playing by the rules, we create our own rules in building digital products. At Wolfpack Digital, we use proven methods to build flawless web apps in Ruby on Rails. Still, we want to keep the innovation going. This is how, at one of our Web Howl Group meetings, we decided to experiment with GraphQL.
In this step-by-step tech article, we are going to cover the following topics:
- What is GraphQL;
- How to use GrahpQL in a Rails application;
- How to write GraphQL Queries and Mutations using Rails;
- What graphiql has to offer, in practice.
What Is GraphQL?
GraphQL is a query language for your API, and a server-side runtime for executing queries by using a type system you define for your data. GraphQL isn’t tied to any specific database or storage engine and is backed by your existing code and data instead.
Basically, GraphQL is a modern way of building and querying APIs. But the cool thing is that, unlike REST APIs, it lets the client determine what data to return. Pretty cool, right?
GraphQL comes with some specific features, and I’m going to present them briefly before starting with our example.
- Query — It’s used to fetch data from the database. It’s similar to the GET requests from a REST API.
- Mutation — It’s used to insert, modify or delete data in the database.
- Type — It’s used to define the datatypes of the objects. Think of them as the models we have in rails. They can contain fields and functions, just like a model.
Setup the Rails Application
Enough with the introduction, let’s generate a Rails application. It will be a simple API for blog posts.
Let’s start by running the following command:
rails new blogposts_graphql_api — skip-test — api
Of course, this is just an example so I’ll skip the tests, but don’t do that on a real application.
The next step is to create two models: User which contains email
and name
(I know what you are going to say, but this is just a demo) and a blogpost which has a user_id
, title
, and body
.
bin/rails g model User email:string name:string
bin/rails g model Blogpost user:belongs_to title:string body:text
Don’t forget to create and migrate the database.
bin/rails db:create
bin/rails db:migrate
Open the app/models/blogpost.rb
file and add the following line:
belongs_to :user
Similarly, add has_maby :blogposts
inside the app/models/user.db
file. This is just to define the relations between the models inside the Rails application.
▶ See the commit here.
Add GraphQL Dependencies
The next thing I’m going to do is install the graphql-ruby gem by adding it in the Gemfile. I’ll also add the graphiql-rails gem in the development group only, which is used to visually test the API, check the documentation. But we’ll see how to use it later.
gem 'graphql', '~> 1.9', '>= 1.9.4'group :deveopment do
gem 'graphiql-rails', '~> 1.7'
....
end
We need to run bundle install
to install the new dependencies and then generate the files and folders needed by GraphQL by running bin/rails g graphql:install
NOTE: you may need to run thebundle install
command again in order to also install graphiql
gem.
We can now generate the User
and Blogpost
GraphQL objects by running:
rails generate graphql:object user
rails generate graphql:object blogpost
We’ll also add the /graphql
and /graphiql
routes in the config/routes.rb
file.
Rails.application.routes.draw do if Rails.env.development? mount GraphiQL::Rails::Engine, at: '/graphiql', graphql_path: 'graphql#execute' end post '/graphql', to: 'graphql#execute'end
▶ You can see all the changes from this section in this commit.
Add Faker and Some Random Data
NOTE: This step is optional
Before we start playing with GraphQL we should make sure we have some data in our database first. In order to do that, I’ll add the faker gem.
I simply added gem ‘faker’, git: ‘https://github.com/stympy/faker.git', branch: ‘master’
in the Gemfile
and ran bundle install
.
After we have the gem installed we can add the following in the db/seeds.rb
file
5.times do
user = User.create(
email: Faker::Internet.unique.email,
name: Faker::Movies::StarWars.unique.character
) 3.times do
Blogpost.create(
user: user,
title: Faker::Movies::StarWars.quote,
body: Faker::Lorem.paragraphs(rand(2..8)).join('.')
)
end
end
After we run bin/rails db:seed
we’ll have 5 users, each having 3 blog posts in our database.
▶ See the commit here.
Create Our First Queries and Mutations
Now that we have everything set up, we can start building our first queries and mutations.
Queries
We’ll start by opening the app/graphql/types/user_type.rb
file and add the following:
module Types
class UserType < Types::BaseObject
field :id, ID, null: false
field :name, String, null: true
field :email, String, null: true
field :blogposts, [Types::BlogpostType], null: true
field :blogposts_count, Integer, null: true def blogposts_count
object.blogposts.count
end
end
end
This is not that different than an Active Model Serializer file, we can see that we are mapping the database fields, setting their types and we also have a function called blogposts_count
. This is just to see how to use a function, I know using thatcounter_cache
would be better.
Also, we notice that the field blogposts
is returning an array of Types:BlogpostType
which was not defined yet. So, let’s go ahead and open the app/graphql/types/blogpost_type.rb
file and add the following:
module Types
class BlogpostType < Types::BaseObject
field :id, ID, null: false
field :title, String, null: true
field :body, String, null: true
field :body_short, String, null: true
field :user_id, ID, null: false def body_short
object.body&.truncate(20)
end
end
end
Now that we have these two types defined, we need to register them in the base query type. I order to do that, we simply open app/graphql/types/query_type.rb
and do the following:
- Register the
users
field. - Define the
users
method which queries all the users - Register the
user
field — similar to the show method - Define the
user
method — responsible for fetching a user from the database.
module Types
class QueryType < Types::BaseObject
field :users, [Types::UserType], null: false field :user, Types::UserType, null: false do
argument :id, ID, required: true
end def users
User.all
end def user(id:)
User.find(id)
end
end
end
It’s finally the time to open the rails application and start testing the application.
Run bin/rails server
and go to http://localhost:3000/graphiql
We’ll see the graphiql
interface and we can start writing queries in the left side. We’ll start with a simple query which returns all the users and the blogposts for each user. This is where the hierarchical design of GraphQL is an advantage because we can see how easily we can get all the blog posts for a user from the database.
query {
users {
name
id
blogpostsCount blogposts {
id
title
bodyShort
body
}
}
}
You can see how easy it is to define queries and chose what we get on the client. If we didn’t need the blog posts, we would just remove them from the query.
Let’s see how we send an argument to a query:
query {
user(id: 6) {
name
id
blogposts {
title
bodyShort
user {
id
}
}
}
}
We simply pass the (id: ..)
on the query and voila — we get only one user.
PS: Did you see the docs button on the top right corner? GraphiQL provides documentation for all our queries and mutations. How cool is that?
▶ You can check the commit for this part here.
Mutations
Now that we know how to query data, let’s see how to make mutations.
For convenience, I’ll create a BaseMutation
class — app/graphql/mutations/base_mutation.rb
module Mutations
class BaseMutation < GraphQL::Schema::RelayClassicMutation;end
end
Next, I’ll add the app/graphql/mutations/create_user.rb
file and write the logic for creating the user. We need to define a resolve
method and define the arguments for our mutation and the fields which can be returned by it.
module Mutations
class CreateUser < BaseMutation
argument :name, String, required: true
argument :email, String, required: true field :user, Types::UserType, null: true
field :errors, [String], null: true def resolve(name:, email:)
user = User.new(name: name, email: email) if user.save
{
user: user,
errors: []
}
else
# Failed save, return the errors to the client
{
user: nil,
errors: user.errors.full_messages
}
end
end
end
end
It’s quite easy to understand what’s happening here, the resolve method is used when a mutation is called and it’s responsible for performing the INSERT in the database and returning the result (or errors).
Next, we need to open the app/graphql/types/mutation_type.rb
file and ad the create_user
mutation.
field :create_user, mutation: Mutations::CreateUser
Let’s see how to use it. We simply send a mutation from graphiql.
mutation {
createUser(input: { name:"Luke Skywalker", email: "luke@lucas-films.com" })
{
user {
name
blogposts {
title
}
}
errors
}
}
We can see that the keyword changed from the query to mutation, next we specify the mutation we want to call and then we specify the input (name and email).
The next lines define what we expect to get from the mutation — a user type or some errors.
▶ You can check the full commit here.
Conclusions and Next Steps
This was just a brief example on how to use GraphQL with Ruby on Rails, but make sure you check the GraphQL documentation because it has a lot more to offer.
Of course, there are some limitations, caching it’s harder to achieve, and n+1 queries may become an issue. Luckily there are solutions for this, like the goldiloader gem.
Also, sending files can be difficult, because encoding the files to base64 is time and resources consuming. Of course, you could always define an API for storing files and use it together with GraphQL.
GraphQL looks definitely promising, but it’s not mature enough to build applications just by using it, but I think used together with a REST API it can bring value and save time on complex projects.
You can check the entire code here where I added more mutations, error handling and examples.
Enough with back-end experiments. Curious how does it look like on the front-end side? Our tech lead wolf, Sasha, will teach you how to connect this API to Nuxt.js. We re about to publish the 2nd part of the article. Hit the “follow” button to be the first to get the insights.
Want to know what Wolves are up to? Check our blog posts with tech trends, how-to articles, and tips for entrepreneurs.
Want to work with us? Because Wolfpack Digital is hiring.
Want us to build your app? Because Wolfpack Digital is the right app development agency for that.