Getting Started with Hanami and GraphQL
Introduction#
Hanami 2.0 is a productive Ruby framework that quickly supports you in building API applications.
Today we will see how to get started with Hanami and GraphQL in ten steps.
We will create the app and a simple code to support the GraphQL schema with a simple Query, including a request spec. The theme is a classic Star Wars schema.
As a prerequisite, you’ll need Ruby 3.2 and Hanami 2.0.3.
Steps#
1. Generate the app#
Generate a new Hanami 2.0 app.
⚡ hanami new star_wars
2. Create a request spec#
Hanami encourages BDD/TDD, so let’s start with a failing spec.
# frozen_string_literal: true
require "json"
RSpec.describe "GraphQL", type: :request do
it "is successful" do
query = <<~QUERY
query getFilm($filmId: ID!) {
film(id: $filmId) {
title
}
}
QUERY
variables = {"filmId" => "1"}
post "/graphql", {query: query, variables: variables}, {"CONTENT_TYPE" => "application/graphql"}
expect(last_response).to be_successful
expect(last_response.headers).to include("Content-Type" => "application/graphql; charset=utf-8")
expect(JSON.parse(last_response.body)).to eq("data" => {"film" => {"title" => "A New Hope"}})
end
end
Keep running this spec with bundle exec rake.
3. Add the GraphQL gem#
Add the GraphQL gem.
⚡ bundle add graphql
Add the new GraphQL MIME Type to the application (config/app.rb).
config.actions.formats.add(:graphql, "application/graphql")
4. Add the persistence layer#
Hanami 2.0 comes without a persistence layer. We will ship it with the 2.2 version.
For now, mimic the persistency layer by adding a struct (app/structs/film.rb):
# auto_register: false
# frozen_string_literal: true
module StarWars
module Structs
Film = Data.define(:id, :title)
end
end
…and a repository (app/repositories/film_repository.rb).
# frozen_string_literal: true
module StarWars
module Repositories
class FilmRepository
def find(id)
Structs::Film.new(id: id, title: "A New Hope")
end
end
end
end
The # auto_register: false magic comment will tell the Hanami application to not auto-register the struct as a component.
5. Add the Film type#
Add a GraphQL Type for the Film (app/graphql/types/film.rb).
# auto_register: false
# frozen_string_literal: true
require "graphql/schema/object"
module StarWars
module Graphql
module Types
class Film < GraphQL::Schema::Object
field :title, String, null: false
end
end
end
end
6. Add the GraphQL Query#
Add the GraphQL Query Type (app/graphql/query.rb).
# auto_register: false
# frozen_string_literal: true
require "graphql/schema/object"
module StarWars
module Graphql
class Query < ::GraphQL::Schema::Object
description "The query root of this schema"
# First describe the field signature:
field :film, Types::Film, "Find a film by ID" do
argument :id, ID
end
# Then provide an implementation:
def film(id:)
Repositories::FilmRepository.new.find(id)
end
end
end
end
7. Add the GraphQL schema#
Add the GraphQL Schema (app/graphql/schema.rb).
# auto_register: false
# frozen_string_literal: true
require "graphql"
require "graphql/schema"
module StarWars
module Graphql
class Schema < ::GraphQL::Schema
query Graphql::Query
end
end
end
8. Generate a Hanami Action#
Now that we have set up the GraphQL part, we can generate an action.
⚡ bundle exec hanami generate action graphql.show --url=/graphql --http=POST
Remove the unit test (rm spec/actions/graphql/show_spec.rb).
9. Edit the Hanami Action#
Edit the action (app/actions/graphql/show.rb).
# frozen_string_literal: true
module StarWars
module Actions
module Graphql
class Show < StarWars::Action
format :graphql
params do
required(:query).filled(:string)
optional(:variables).maybe(:hash)
end
def initialize(schema: StarWars::Graphql::Schema, **)
@schema = schema
super(**)
end
def handle(req, res)
halt 400, req.params.errors unless req.params.valid?
req.params => {query:, variables:}
res.body = schema.execute(query, variables:).to_json
end
private
attr_reader :schema
end
end
end
end
10. Dump the GraphQL schema#
Dump the schema by adding the following Rake task in Rakefile.
# frozen_string_literal: true
require "hanami/rake_tasks"
require "graphql/rake_task"
GraphQL::RakeTask.new(schema_name: "StarWars::Graphql::Schema", directory: "public")
⚡ bundle exec rake graphql:schema:dump
Conclusion#
That’s ten simple steps to get started with Hanami 2.0 and GraphQL.