Learning GraphQL with Rails

Taylor Wu
10 min readJul 23, 2018

--

This is a tutorial for learning graphQL with react and rails. Before we get started this is a walk through for people who already know rails and react. We will primarily focus on the graphQL side.

GraphQL vs REST

Before using GraphQL we must understand the why. GraphQL solves many of the problems that are introduced when using RESTful convention. Lets go through some examples.
Say we have a database with authors and books belongs to them.

Flexibility: Lets pretend we are on our author page and we want to display data about the author and their books. Here is how we would get data with RESTful convention vs graphQL:

With the REST API we would have to make different endpoints to get different data. This is two different api calls to our server every time a user lands on the author page. With graphql the client can decide what data it wants back from the server.

Overfetching Data with REST:

What if we only want the name of the author? Well with rails we can use the Author.all active record helper, but we also get back fluff we do not need like created_at or updated_at

With graphQL we can just query which columns we want.

Underfetching Data and n + 1

Lets say we want to display all our authors and their books in one page. So our api calls would look like this.

With REST we would have to make a request to /authorsand then after that for each user we need to call /authors/:id/books. If we had 100 users we would have to make 101 requests to our server to get this data. GraphQL can just fetch this same data but with only one request. This will improve performance on your server.

Now that we discuss the benefits of graphQL lets start building something!

App we are building:

We are building a simple app that will display authors and those authors will have books.

High Level View:

Our Client side will be using react and an graphQL client to fetch and display the data.

The graphQL resolver is our ruby app and will take in the requests from the client and tell the database what data it needs to bring back. These one more thing inside the graphQL resolver as well that we need to address. We will have to create objects we can query from. These objects will be very similar to the tables in our database.

Technology we will use:

  • Rails (our sever)
  • GraphQL-ruby (for graphQL server)
  • React (for client side rendering)
  • Apollo (for client side graphQL)
  • Semantic UI React (Styling)
  • React Router DOM (Client side routing)

We will be utilizing a npm package called create-repack-app this will help us get a rails project running with react real quick.

we need this npm package install globally as well as create-react-app

$ npm install create-react-app -g
$ npm install create-repack-app -g

now lets build our project:

$ create-repack-app graphql-bookstore# creating the models$ rails g model Author name age:integer
$ rails g model Book title genre author:belongs_to

Now lets the gems we need

gem 'graphql', '~> 1.8', '>= 1.8.5' #getting graphql in our rails appgroup :development, :test do  gem 'pry' # debugging  gem 'faker' # seed dataend

Install gems:

$ bundle install

Seeds some data in db/seeds.rb

Setup associations in the model:

app/models/author.rb

now lets create the db and seed some data:

$ bundle exec rake db:create db:migrate db:seed

Now we need to set up our graphQL endpoint. we can use graphql generator for this.

$ rails g graphql:install

Now lets take a look at some of the files it generated.

create  app/graphql/types
create app/graphql/types/.keep
create app/graphql/graphql_author_schema.rb
create app/graphql/types/base_object.rb
create app/graphql/types/base_enum.rb
create app/graphql/types/base_input_object.rb
create app/graphql/types/base_interface.rb
create app/graphql/types/base_union.rb
create app/graphql/types/query_type.rb
add_root_type query
create app/graphql/mutations
create app/graphql/mutations/.keep
create app/graphql/types/mutation_type.rb
add_root_type mutation
create app/controllers/graphql_controller.rb
route post "/graphql", to: "graphql#execute"

app/graphql/types

When we are using graphQL we have to build objects types that can be queried from the client. This will look very similar to the db schema.

app/graphql/graphql_author_schema.rb

This is the schema of our project. Its letting our app know where our mutations and query start.

class GraphqlAuthorSchema < GraphQL::Schema  mutation(Types::MutationType)  query(Types::QueryType)end

add_root_type query and mutation

Two key words here, query and mutation. Whenever we make a any request to a server we must have a http action attached to it. GET/POST/PUT/DELETE

A query is a graphQL call that only gets data back. A mutation is when its changing data.

config/route

This adds a single endpoint in rails route. Now we do not need multiple endpoints in our server because graphQL can handle all the requests.

Now let’s build out our graphQL object types.

$ touch app/graphql/author_type.rb
$ touch app/graphql/book_type.rb

Each field needs to have a data type. and the books field for Author is an array because an author can have many books.

Now lets build a query that can get all the authors.

app/graphql/types/query_type.rb

This well create a query where we can get all the authors.

Before we can query from our server we will need to start it

$ bundle exec rails s

Now we need a tool to help us fetch data from graphQL. I love to use GraphiQL.

The left panel is the query we are running. The middle panel is the what gets returned from the query. The most interesting part is the right panel, its auto generating documentation from our types.

Now lets build a query to get one author back.

What the query looks like in GraphiQL:

Cool. Now lets finish writing out the book queries.

Now we are able to query for all the data we need. Its time to build out the mutations.

We need to create a mutation class that will inherit from the graphql schema mutation.

create this new file.

$ touch app/graphql/mutations/base_mutation.rb # root class
$ touch app/graphql/mutations/create_author.rb # our first mutation

base_mutation.rb

class Mutations::BaseMutation < GraphQL::Schema::Mutationend

create_author.rb

Now we need to let the root mutation know about our mutation. app/graphql/types/mutation_type.rb

class Types::MutationType < Types::BaseObject  field :create_author, mutation: Mutations::CreateAuthorend

Lets create a new author through the graphiQL tool.

Lets finish up the rest of our mutations.

$ touch app/graphql/mutations/update_author.rb
$ touch app/graphql/mutations/delete_author.rb
$ touch app/graphql/mutations/create_book.rb
$ touch app/graphql/mutations/update_book.rb
$ touch app/graphql/mutations/delete_book.rb

app/graphql/types/mutation_type.rb

class Types::MutationType < Types::BaseObject  ...  field :update_author, mutation: Mutations::UpdateAuthor  field :delete_author, mutation: Mutations::DeleteAuthorend

Mutations:

Now lets test them with graphiQL:

Time to finish out the book mutations:

class Types::MutationType < Types::BaseObject  ...  field :create_book, mutation: Mutations::CreateBook  field :update_book, mutation: Mutations::UpdateBook  field :delete_book, mutation: Mutations::DeleteBookend

Tests with graphiQL:

Now that our server is set up we can now start building out the client.

Inside our rails app there should be a client folder. This folder is created from the create-react-app CLI tool that facebook provides for us as a react started kit.

NPM packages we will need.

  • apollo-boost: Package containing everything you need to set up Apollo Client
  • react-apollo: View layer integration for React
  • graphql-tag: Necessary for parsing your GraphQL queries
  • graphql: Parses your GraphQL queries

Lets add these to our client package.json

# make sure you when you do this you're in the client directory$ npm install apollo-boost react-apollo graphql-tag graphql --save
$ npm install semantic-ui-react semantic-ui-css --save # styling
$ npm install react-router-dom --save #client side routing
$ npm run start

Our client side is proxied to localhost:3001 and our server app should be running on that port. We also need to let the apollo client know about the url of the server so lets add that in to the react project.

We’ll be adding this configuration inside the src/index.js and lets add the semantic-css file in there as well.

This is what that query logs out in the console. It made a POST to /graphql and responded back with an object with our data.

Now that we know how the apollo client is getting data lets delete that query from index.js

Lets build out the routes inside src/App.js

# make sure you're in your client folder$ mkdir src/components$ touch src/components/Authors.js
$ touch src/components/Author.js

Lets stub out those components.

Now lets hook up our Authors component to the apollo client so we can start getting authors from the db.

Our Apollo Client is set up so we ready to request data with a Query component. Query is a React component exported from react-apollo that shares GraphQL data with the UI.

So the Query component has a prop of query. This is where we will write our query. And inside that query component we will return a callback with the response from the server. After that we will check if theres an error or loading state that returns some html that lets the user know if its loading or not. Else we will loop through the authors we get back and render a card that is clickable and will take them to the /authors/:id route.

One thing we should do is move the query to its own file so maybe in the future we can reuse it.

Lets make a new folder where we can store our queries.

$ mkdir src/queries
$ touch src/queries/getAuthors.js
// Authors.jsimport query from '../queries/getAuthors';...return (
<Query
query={query}
>
...
)

Now lets build out our Author.js component that will display all the information about the author and their books.

$ touch src/queries/getAuthor.js

queries/getAuthor.js

components/Author.js

The difference between this and our Authors.js component is that we pass down the variable props to the query.

Now that we are done with building out the queries for our authors. We can now focus on creating, updating, and deleting authors.

We are going to add a form in the Authors.js component.

$ touch src/components/AuthorForm.js
// Authors.js
...
import AuthorForm from './AuthorForm';
import { Header } from 'semantic-ui-react';
...<Container>
<Header>Create Author</Header>
<AuthorForm />
...
</Container>
# in terminal$ mkdir src/mutations
$ touch src/mutations/addAuthor.js

Now we should add our mutations in its own directory:

$ mkdir src/mutations
$ touch src/mutations/addAuthors.js

components/AuthorForm.js

Lets try to add an author through the form. Notice that when we do add one it does not update our UI with the new added author. One option we have is to pass in an option inside the addAuthor function called refetchQueries. Once the mutation is finish it will call whatever query that is passed into it.

// AuthorForm.js...
import query from '../queries/getAuthors';
...
handleSubmit = (addAuthor) => {
...
addAuthor({
variables: { name, age: parseInt(age) }, refetchQueries: [{ query }] });
...
}

Now every time that we add a new author it will call the query to get all our authors.

Moving on to the delete function. I want to add a button on the Author card to delete it. Lets move the author card to it own component, so we can keep our Authors component clean.

// Authors.js...
import AuthorCard from './AuthorCard';
...
<Container>
...
<Card.Group centered>
{data.authors.map(author => (
<AuthorCard key={author.id} author={author} />
))}
</Card.Group>
</Container>

Creating the AuthorCard Component

$ touch src/components/AuthorCard.js
$ touch src/mutations/deleteAuthor.js # creating delete mutation

deleteAuthor.js

components/AuthorCard.js

Last CRUD action we need to build out for author is update.

Lets add that to the Author component.

Building out the mutation:

$ touch src/mutations/updateAuthor.js
$ touch src/components/AuthorUpdateForm.js

AuthorUpdateForm.js

One thing I want to point out here is that we don’t have a refetchQuery in our mutation. That because for each object we query, apollo will give it a global id for each object. When theres a mutation for that object apollo will update its cache of all the query that has that object in its data. I’ve added an optimisticResponse which returns a object of what I think the server will return with and the UI will get updated before a response from the server. This will make a great user experience.

Thats all I have for the follow along. I want to challenge you to finish up the rest of the app, by adding CRUD actions to it. I’ll have the link to my github with the finished App here.

Thank you for reading!

--

--