The author selected Free Press to receive a donation as part of the Write for DOnations program.
GraphQL is a strongly typed query language for APIs and a server-side runtime for executing those queries with your existing data. GraphQL allows clients to fetch multiple resources from the server in a single request by giving clients the ability to specify the exact data needed in the query. This removes the need for multiple API calls. GraphQL is language and database independent, and thus can be implemented in almost every programming language alongside any database of choice.
In this tutorial, you will build a GraphQL-powered Ruby on Rails API for taking notes. When you are finished, you will be able to create and view notes from the API using GraphQL.
If you would like to take a look at the code for this tutorial, check out the companion repository for this tutorial on the DigitalOcean Community GitHub.
To follow this tutorial, you’ll need:
In this step, you will set up a new Rails API application and connect it to a PostgreSQL database. This will serve as the foundation for the note-taking API.
Rails provides commands that make building modern web applications faster for developers. These commands can perform actions that range from creating a new Rails application to generating files required for app development. For a full list of these commands and what they do, run the following command in your terminal window:
- rails -h
This command yields an extensive list of options you can use to set the parameters of your application. One of the commands listed is the new
command, which accepts an APP_PATH
and creates a new Rails application at the specified path.
Create a new Rails application using the new
generator. Run the following command in your terminal window:
- rails new rails_graphql -d=postgresql -T --api
This creates a new Rails application in a directory named rails_graphql
and installs the required dependencies. Let’s go over the flags associated with the new
command:
-d
flag pre-configures the application with the specified database.-T
flag instructs Rails to not generate test files since you won’t be writing tests in this tutorial. You can also use this flag if you plan to use a different testing framework other than the one provided by Rails.--api
flag configures a Rails application with only the files required for building an API with Rails. It skips configuring settings needed for browser applications.Once the command is done running, switch to the newly created rails_graphql
directory, which is the application’s root directory:
- cd rails_graphql
Now that you have successfully set up a new Rails API application, you have to connect it to a database before you can run the app. Rails provides a database.yml
file found in config/database.yml
, which contains configurations for connecting your app to a different database for different development environments. Rails specifies a database name for different development environments by appending an underscore (_
) followed by the environment name to your app’s name. You can always change any environment database name to whatever you choose.
Note: You can alter config/database.yml
to choose the PostgreSQL role you would like Rails to use to create your database. If you created a role that is secured by a password, follow the instructions in Step 4 of How To Use PostgreSQL with Your Ruby on Rails Application on Ubuntu 18.04 or How To Use PostgreSQL with Your Ruby on Rails Application on macOS to configure your role.
Rails includes commands for creating and working with databases. With your database credentials in place, run the following command in your terminal window to create your databases:
- rails db:create
The db:create
command creates a development
and test
database based on the information provided in the config/database.yml
file. Running the command yields the following output:
OutputCreated database 'rails_graphql_development'
Created database 'rails_graphql_test'
With your application now successfully connected to a database, you can test the application to ensure it works. Start your server with the following command if you are working locally:
- bundle exec rails server
If you are working on a development server, you can start your application by specifying the IP address the server should bind to:
- bundle exec rails server --binding=your_server_ip
Note: The server listens on port 3000
. If you’re working on a development server, ensure that you have opened port 3000
in your firewall to allow connections.
The rails server
command launches Puma, a web server for Ruby distributed with Rails. The --binding=your_server_ip
command binds the server to any IP you provide.
Once you run this command, your command prompt will be replaced with the following output:
Output=> Booting Puma
=> Rails 6.0.2.1 application starting in development
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 4.3.1 (ruby 2.6.3-p62), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://127.0.0.1:3000
* Listening on tcp://[::1]:3000
Use Ctrl-C to stop
To run your application, navigate to localhost:3000
or http://your_server_ip:3000
in your browser. You’ll see the Rails default welcome page:
The welcome page means you have properly set up your Rails application.
To stop the server, press CTRL+C
in the terminal window where the server is running.
You have successfully set up a Rails API application for a note-taking API. In the next step, you will set up your Rails API application to receive and execute GraphQL queries.
In this step, you will configure your Rails API application to work with GraphQL. You will install and set up the necessary gems required for GraphQL development in Rails.
As previously mentioned, GraphQL is language agnostic and is implemented in many programming languages. The graphql-ruby gem is the Ruby implementation for GraphQL. GraphQL also provides an interactive in-browser IDE known as GraphiQL for running GraphQL queries. The graphiql-rails
gem helps you add GraphiQL to your development environment.
To install these dependencies, open the project’s Gemfile
for editing, using nano or your favorite text editor:
- nano Gemfile
Add the graphql
and graphiql-rails
gems to your Gemfile. You can add the graphiql
gem anywhere, but the graphiql-rails
gem should be added under the development dependencies:
...
group :development do
gem 'listen', '>= 3.0.5', '< 3.2'
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
gem 'graphiql-rails'
end
gem 'graphql', '1.9.18'
...
Save and close the file when you are done adding the gems.
In your terminal window, use the following command to install the gems:
- bundle install
The output shows that the gems are installed.
The graphql
gem provides generators to create various files. To view the available generators, run the following command in your terminal window:
- rails generate
The generators prefixed with graphql:
are the ones associated with the graphql
gem.
You will use the graphql:install
command to add graphql-ruby
boilerplate code to the application and mount GraphiQL in your development environment. The boilerplate code will include all the files and directory needed for the graphql-ruby
gem to work with Rails.
In your terminal window, run the following commands:
- rails g graphql:install
This command generates several files, including a graphql_controller.rb
file located at app/controllers/graphql_controller.rb
and a graphql
directory at app/graphql
which contains files required to get started with GraphQL in Rails. It also adds a /graphql
HTTP POST
route in the routes file located at config/routes.rb
. This route is mapped to the app/controllers/graphql_controller.rb#execute
method which handles all queries to the GraphQL server.
Before you can test the GraphQL endpoint, you need to mount the GraphiQL engine to the routes file so you can access the GraphiQL in-browser IDE. To do this open the routes file located at config/routes.rb
:
- nano ~/rails_graphql/config/routes.rb
Add the following code to the file to mount the GraphiQL engine in the development environment:
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"
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
This mounts the GraphiQL engine to the /graphiql
path and directs all queries to the graphql#execute
method.
Since this is an API application created with the --api
flag, it does not expect to render any page in the browser. To make the GraphiQL editor show up in the browser, you need to make a couple of small changes to your application’s configuration.
First, open the application.rb
file located at config/application.rb
:
- nano ~/rails_graphql/config/application.rb
Next, uncomment the require "sprockets/railtie"
line:
require_relative 'boot'
require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "active_storage/engine"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_mailbox/engine"
require "action_text/engine"
require "action_view/railtie"
require "action_cable/engine"
require "sprockets/railtie"
# require "rails/test_unit/railtie"
...
Save and close the file after uncommenting the line.
Now create a config
directory at app/assets
:
- mkdir -p app/assets/config
Next, create a manifest.js
file in the newly created config
directory. The manifest.js
file specifies additional assets to be compiled and made available to the browser:
- nano app/assets/config/manifest.js
Add the following code to the file which tells Rails to precompile the graphiql/rails/application.css
and graphiql/rails/application.js
files so Rails can serve them to your browser:
//= link graphiql/rails/application.css
//= link graphiql/rails/application.js
Save and close the file.
With that done, you can test your GraphQL endpoint. Restart your development server, and in your browser, navigate to localhost:3000/graphiql
or http://your_server_ip:3000/graphiql
. The GraphiQL query editor displays in your browser:
The left side of the GraphiQL IDE accepts GraphQL queries and the right side displays results of the run query. The GraphiQL query editor also has a syntax highlighter and a typeahead hinter powered by your GraphQL Schema. Together, these help you make a valid query.
To try a Hello World
example, clear out the default text in the editor’s left pane and type in the following query:
query {
testField
}
Click the Play icon button in the header and you’ll recieve a successful response on the screen, as shown in the following figure:
You have successfully set up your Rails API application to work with GraphQL and tested your GraphQL endpoint to confirm it works. In the next step, you will create GraphQL types for your application.
GraphQL depends on its Types and Schema to validate and respond to queries. In this step, you will create a Note model and the GraphQL types required in your note-taking API.
A GraphQL type consists of fields
and arguments
which, in turn, define the fields and arguments that can appear in any GraphQL query that operates on that type. These types make up a GraphQL Schema. GraphQL defines the following types:
query
type and may or may not have a mutation
type.Int
, Float
, String
, Boolean
, and ID
.There are other types, including Union
, List
, Non-Null
, and Interface
. You can find a list of available GraphQL types in the official GraphQL documentation.
For this application, you will create a Note
model and a Note
object and input type. The Note
model will represent the database table that will store your notes while the Note
object and input type will define the fields and arguments that exists on a Note
object.
First, create a Note
model using the generate model
subcommand provided by Rails and specify the name of the model along with its columns and data types. Run the following command in your terminal window:
- rails generate model note title:string:index body:text
This command creates a Note
model with two fields: title
, with the type string
, and body
, with the type text
. The command also adds a database index
on the title
column. It generates these two files:
note.rb
file located at app/models/note.rb
. This file will hold all model-related logic.20200617173228_create_notes.rb
file (the number at the beginning of the file will differ, depending on the date you run the command) located at db/migrate/20200617173228_create_notes.rb
. This is a migration file that holds the instruction for creating a corresponding notes
table in the database.To execute the instructions in the migration file, you’ll use the db:migrate
subcommand which executes the instruction in your migration files. Run the following command in your terminal window:
- rails db:migrate
Once the command runs successfully, you will see output similar to the following:
Output== 20200617173228 CreateNotes: migrating ======================================
-- create_table(:notes)
-> 0.0134s
-- add_index(:notes, :title)
-> 0.0073s
== 20200617173228 CreateNotes: migrated (0.0208s) =============================
With the note model in place, next you’ll create a NoteType
. A valid note object is expected to have an id
, a title
, and text
. Run the following command in your terminal window to create a NoteType
:
- rails generate graphql:object Note id:ID! title:String! body:String!
The command instructs Rails to create a GraphQL object type called Note
with three fields: an id
field with a type of ID
, and the title
and body
fields, each with a String
type. The exclamation point (!
) appended to the field type indicates that the field should be non-nullable, meaning that the field should never return a null value. Non-nullable fields are important, as they serve as a form of validation that guarantees which fields must be present whenever GraphQL objects are queried.
Running the preceding command creates a note_type.rb
file located at app/graphql/types/note_type.rb
containing a Types::NoteType
class with three non-nullable fields.
Lastly, you will create a NoteInput
type to define the arguments required to create a note. Start by creating an input
directory under app/graphql/types
. The input directory will house input types:
- mkdir ~/rails_graphql/app/graphql/types/input
Note: It’s not a requirement to create input types in the input directory; it is merely a common convention. You can decide to keep all your types under the types directory and exclude nesting the class under an Input
module whenever you’re accessing it.
In the ~/rails_graphql/app/graphql/types/input
directory, create a note_input_type.rb
file:
- nano ~/rails_graphql/app/graphql/types/input/note_input_type.rb
Add the following code to the file to define the fields for the Input
type:
module Types
module Input
class NoteInputType < Types::BaseInputObject
argument :title, String, required: true
argument :body, String, required: true
end
end
end
In the note_input_type.rb
file, you added a Types::Input::NoteInputType
class that inherits from the Types::BaseInputObject
class and accepts two required arguments; title
and body
, both of a string type.
You’ve created a model and two GraphQL types for your note-taking app. In the next step, you will create queries to fetch existing notes.
Your GraphQL-powered API is gradually coming together. In this step you’ll create two queries; one to fetch a single note by id
and another to fetch all notes. The GraphQL query
type handles the fetching of data and can be likened to a GET request in REST.
First, you’ll create a query to fetch all notes. To start, create a queries
directory to house all queries:
- mkdir ~/rails_graphql/app/graphql/queries
In the app/graphql/queries
directory, create a base_query.rb
file from which all other query classes will inherit:
- nano ~/rails_graphql/app/graphql/queries/base_query.rb
Add the following code to the base_query.rb
file to create a BaseQuery
class that other query classes will inherit from:
module Queries
class BaseQuery < GraphQL::Schema::Resolver
end
end
In the base_query.rb
file, you added a Queries::BaseQuery
class that inherits from the GraphQL::Schema::Resolver
class. The GraphQL::Schema::Resolver
class is a container that can hold logic belonging to a field
. It can be attached to a field
with the resolver:
keyword.
The Queries::BaseQuery
class can also contain any code you intend to reuse across multiple query classes.
Next, create a fetch_notes.rb
file in the queries
directory. This file will hold the logic for fetching all existing notes, and will be attached to a field
in the query type file:
- nano ~/rails_graphql/app/graphql/queries/fetch_notes.rb
Add the following code to the file to define the return object type and resolve the requested notes:
module Queries
class FetchNotes < Queries::BaseQuery
type [Types::NoteType], null: false
def resolve
Note.all.order(created_at: :desc)
end
end
end
In the fetch_notes.rb
file, you created a Queries::FetchNotes
class that inherits the Queries::BaseQuery
previously created. The class has a return type
declaration that declares that the data returned by this query should be an array of the already created NoteType
.
The Queries::FetchNotes
also contains a resolve
method that returns an array of all existing notes sorted by their created date in descending order.
The FetchNotes
query is ready to receive and return requests for notes, but GraphQL is still unaware of its existence, to fix that, open the GraphQL query type file located at app/graphql/types/query_type.rb
:
- nano ~/rails_graphql/app/graphql/types/query_type.rb
The query_type.rb
file is the entry point for all GraphQL query
types. It holds the query fields, and their respective resolver methods. Replace the sample code in the file with the following:
module Types
class QueryType < Types::BaseObject
# Add root-level fields here.
# They will be entry points for queries on your schema.
field :fetch_notes, resolver: Queries::FetchNotes
end
end
In the query_type.rb
file, you added a fetch_notes
field and attached it to the Queries::FetchNotes
class using a resolver:
. This way whenever the fetch_notes
query is called, it executes the logic in the resolve
method of the Queries::FetchNotes
class.
In order to test your query, you need some data to fetch, but you currently don’t have any notes in your database. You can fix that by adding some seed data to your database. Open the seeds.rb
file located at db/seeds.rb
:
- nano ~/rails_graphql/db/seeds.rb
Add the following code to the file to create five notes:
5.times do |i|
Note.create(title: "Note #{i + 1}", body: 'Lorem ipsum saves lives')
end
Save and close the file after adding the code.
Open your project’s root directory in another terminal window and run the following command to run the code in the seed.rb
file:
- rails db:seed
This creates 5 notes in the database.
With data in your database, and your development server running, navigate to localhost:3000/graphiql
or http://your_server_ip:3000/graphiql
in your browser to open your GraphiQL IDE. In the left side of the editor, type in the following query:
query {
fetchNotes {
id
title
body
}
}
This GraphQL query declares a query
operation, indicating you want to make a query request. In the query operation, you called a fetchNotes
field that matches the fetch_notes
query field declared in the API, and included the fields on a note that you want to be returned in your response.
Click the Play icon button in the header. You’ll see a response similar to the following in the output pane:
{
"data": {
"fetchNotes": [
{
"id": "5",
"title": "Note 5",
"body": "Lorem ipsum saves lives"
},
{
"id": "4",
"title": "Note 4",
"body": "Lorem ipsum saves lives"
},
{
"id": "3",
"title": "Note 3",
"body": "Lorem ipsum saves lives"
},
{
"id": "2",
"title": "Note 2",
"body": "Lorem ipsum saves lives"
},
{
"id": "1",
"title": "Note 1",
"body": "Lorem ipsum saves lives"
}
]
}
}
The response contains an array of 5 notes that match the fields declared in the query on the left. If you remove some fields in the query on the left side of the editor and re-run the query, you get a response with only the fields you requested. That’s the power of GraphQL.
Next, you’ll create another query to fetch notes by id
. This query will be similar to the fetch_notes
query, only that it’ll accept an id
argument. Go ahead and create a fetch_note.rb
file in the queries directory:
- nano ~/rails_graphql/app/graphql/queries/fetch_note.rb
Add the following code to the file to find and return a note with the provided id
:
module Queries
class FetchNote < Queries::BaseQuery
type Types::NoteType, null: false
argument :id, ID, required: true
def resolve(id:)
Note.find(id)
rescue ActiveRecord::RecordNotFound => _e
GraphQL::ExecutionError.new('Note does not exist.')
rescue ActiveRecord::RecordInvalid => e
GraphQL::ExecutionError.new("Invalid attributes for #{e.record.class}:"\
" #{e.record.errors.full_messages.join(', ')}")
end
end
end
This defines a Queries::FetchNote
class that inherits from the Queries::BaseQuery
class. This class not only returns a single item that must be of a NoteType
, it also accepts an id
argument with an ID
type. The resolve
method receives the provided id
argument, then finds and returns a note with the provided id
. If no note exists or an error occurs, it is rescued and returned as a GraphQL::ExecutionError
.
Next, you will attach the Queries::FetchNote
class to a query field in the query type file. Open the query_type.rb
file in your editor:
- nano ~/rails_graphql/app/graphql/types/query_type.rb
Add the following code to the file which defines a resolver for fetch_notes
:
module Types
class QueryType < Types::BaseObject
# Add root-level fields here.
# They will be entry points for queries on your schema.
field :fetch_notes, resolver: Queries::FetchNotes
field :fetch_note, resolver: Queries::FetchNote
end
end
To test your new query, ensure your server is running and navigate to localhost:3000/graphiql
or http://your_server_ip:3000/graphiql
in your browser to open your GraphiQL IDE. In the left side of the editor, type in the following query:
query {
fetchNote(id: 1) {
id
title
body
}
}
This query operation requests a fetchNote
field, which corresponds to the fetch_note
query field, and is passed an id
argument. It specifies that we want three fields to be returned in the response.
Run the query by clicking the Play icon button in the header. You will get a response like the following in the output pane:
{
"data": {
"fetchNote": {
"id": "1",
"title": "Note 1",
"body": "Lorem ipsum saves lives"
}
}
}
The response contains a single note that matches the requested id
with fields matching the ones in the request.
In this step, you created GraphQL queries to fetch notes from your API. Next you’ll write mutations to create notes.
In addition to queries, GraphQL also defines a mutation
type for operations that modify server-side data. Just as REST provides POST
, PUT
, PATCH
, and DELETE
requests for creating, updating and deleting resources, GraphQL’s mutation
type defines a convention for operations that cause writes on the server-side. In this step, you’ll create a mutation for adding new notes.
graphQL-ruby
includes two classes for writing mutations. They are:
input
argument required in your mutations, you should use this class.clientMutationId
that is always inserted to the response, and mutations that accepts one argument called input
. This class is used by default when you use the install generator
to add boilerplate GraphQL files to your project.Create an add_note.rb
file in the mutations
directory located at app/graphql/mutations
:
- nano ~/rails_graphql/app/graphql/mutations/add_note.rb
Add the following code to the file to define the mutation for adding new notes:
module Mutations
class AddNote < Mutations::BaseMutation
argument :params, Types::Input::NoteInputType, required: true
field :note, Types::NoteType, null: false
def resolve(params:)
note_params = Hash params
begin
note = Note.create!(note_params)
{ note: note }
rescue ActiveRecord::RecordInvalid => e
GraphQL::ExecutionError.new("Invalid attributes for #{e.record.class}:"\
" #{e.record.errors.full_messages.join(', ')}")
end
end
end
end
This defines a Mutations::AddNote
class that inherits from the Mutations::BaseMutation
class, which is one of the classes created when you ran the install generator
while installing the GraphQL-Ruby gem. The Mutations::AddNote
class receives an argument
with the name params
and a type of NoteInputType
, which you created in Step 3. It also returns a field
called note
that must be a non-null NoteType
type.
The resolve
method of the class receives the params
and converts it to a hash which it uses to create and return a new hash containing the new note. If there’s an error while creating the note, the error is rescued and returned as a GraphQL::ExecutionError
.
Note: The resolve
method in a mutation must return a hash whose symbol matches the field
names.
Like with queries, the Mutations::AddNote
mutation has to be attached to a mutation field using the mutation:
keyword.
Open the mutation type file located at app/graphql/types/mutation_type.rb
in your editor:
- nano ~/rails_graphql/app/graphql/types/mutation_type.rb
Replace the code in the file with the following code, which adds a field for the add_note
with its corresponding mutation class:
module Types
class MutationType < Types::BaseObject
field :add_note, mutation: Mutations::AddNote
end
end
In this code, you added an add_note
field to the mutation type file and attached it to the Mutations::AddNote
class using the mutation:
keyword. When the add_note
mutation is called, it runs the code in the resolve
method of the Mutations::AddNote
class.
To test your new mutation, navigate to localhost:3000/graphiql
or http://your_server_ip:3000/graphiql
in your browser to open your GraphiQL IDE. In the left side of the editor, type in the following query:
mutation {
addNote(input: { params: { title: "GraphQL notes", body: "A long body of text about GraphQL" }}) {
note {
id
title
body
}
}
}
This declares a mutation operation with an addNote
field that accepts a single input
argument, which in turn accepts a param
object with keys that match the NoteInputType
. The mutation operation also includes a note
field that matches the note
field returned by the Mutations::AddNote
class.
Run the mutation in GraphiQL and you’ll see the following results in the output pane:
{
"data": {
"addNote": {
"note": {
"id": "6",
"title": "GraphQL notes",
"body": "A long body of text about GraphQL"
}
}
}
}
The response returned is the newly created note with the fields requested in the mutation request.
With your add_note
mutation now working, your API can fetch and create notes using GraphQL queries and mutations.
In this tutorial, you created a note-taking API application with Ruby on Rails using PostgreSQL as your database and GraphQL as your API query language. You can learn more about GraphQL on its official website. The GraphQL-Ruby gem website also contains some guides to help you work with GraphQL in Rails.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
This worked awesome by the first try. Thank you
After running the mutation query it gives the following error
Is there a way you can have the mutation this way
mutation { addNote(input: { title: “GraphQL notes”, body: “A long body of text about GraphQL” }) { note { id title body } } }
without the params object?
For me it should be this way
argument :input, Types::Input::NoteInputType, required: true