Getting Started with Hanami and GraphQL
What is GraphQL?
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, it gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
What is Hanami?
Hanami is a Ruby MVC web framework comprised of many micro-libraries. It has a simple, stable API, a minimal DSL, and prioritises the use of plain objects over magical, over-complicated classes with too much responsibility.
The natural repercussion of using simple objects with clear responsibilities is more boilerplate code. Hanami provides ways to mitigate this extra legwork while maintaining the underlying implementation.
Project setup
If you haven't already done so, install hanami.
gem install hanami
After hanami is installed on your machine, you can create a new project. Feel free to chose another database or test framework if you like.
hanami new blogs --database=postgres --application-name=api --test=rspec
cd blogs
Define entities
Before we do anything at all, we need entities we can query over our API. Hanami offers a generator for entities which can be invoked by the following command:
hanami generate model author
This will generate an entity and the corresponding test. In this tutorial tests are omitted for brevity but you are encouraged to implement them on your own.
We start out with our author as it's a very simple model. It has a single attribute 'name'.
# lib/blogs/entities/author.rb
class Author
include Hanami::Entity
attributes :name
end
Next we're going to generate another model. A blog.
hanami generate model blog
For our blog, we want a title, content and an author_id to reference the author.
# lib/blogs/entities/blog.rb
class Blog
include Hanami::Entity
attributes :title, :content, :author_id
end
Update database
To be able to store entities, we need to define database tables to hold data. Create a migration for the author model first:
hanami generate migration create_authors
Hanami will generate a migration file with a current timestamp for you under db/migrations
. Open the file and add the following:
Hanami::Model.migration do
change do
create_table :authors do
primary_key :id
column :name, String, null: false
end
end
end
For blogs, we create another migration named create_blog
.
hanami generate migration create_blogs
Inside the migration create another table with columns for our blog:
Hanami::Model.migration do
change do
create_table :blogs do
primary_key :id
column :title, String, null: false
column :content, String, null: false
foreign_key :author_id, :authors
end
end
end
To get the changes to our database, execute
hanami db create hanami db migrate
In order to be able to run database-backed tests, we need to ensure that the test database uses the same schema as our development database. Update the schema by setting HANAMI_ENV
to test
explicitly:
HANAMI_ENV=test hanami db create
HANAMI_ENV=test hanami db migrate
Now that our database is ready, we can go ahead and define mappings for author and blog. Go to lib/blogs.rb
, find the mapping section and add mappings for the new entities.
##
# Database mapping
#
# Intended for specifying application wide mappings.
#
mapping do
collection :blogs do
entity Blog
repository BlogRepository
attribute :id, Integer
attribute :title, String
attribute :content, String
attribute :author_id, Integer
end
collection :authors do
entity Author
repository AuthorRepository
attribute :id, Integer
attribute :name, String
end
end
Introducing Types
After having defined our entities, we can now move on to create GraphQL types. First update your Gemfile and add the following line:
gem 'graphql'
and then run
bundle install
We're going to place type definitions in a dedicated directory to keep them separate from our entities. Furthermore those types are relevant for our web API only and not for the whole application. Create a directory in apps/api/
named types
mkdir -p apps/api/types
and update your web app's application.rb
file to include type definitions in the load path.
load_paths < 'apps/api/types'
# apps/api/types/query_type.rb
require_relative 'author_type'
require_relative 'blog_type'
QueryType = GraphQL::ObjectType.define do
name 'Query'
description 'The query root for this schema'
field :blog do
type BlogType
argument :id, !types.ID
resolve -> (_, args, _) {
BlogRepository.find(args[:id])
}
end
field :author do
type AuthorType
argument :id, !types.ID
resolve -> (_, args, _) {
AuthorRepository.find(args[:id])
}
end
end
# apps/api/types/blog_schema.rb
require_relative 'query_type'
BlogSchema = GraphQL::Schema.define(query: QueryType)
Notice the require_relative
statements at the beginning of some files. This is a workaround because, even though defined in the load path, types don't seem to be auto loaded inside a type definition file.
... and Action
Now that the schema definitions and load path are set up correctly, it is time to create the action that will serve query requests. To generate a new action invoke the following command:
hanami generate action api graphql#show --skip-view
Since we're providing the --skip-view
flag Hanami will not generate a view class and template for this action. The above command will generate a new action where we place query logic.
# apps/api/controllers/graphql/show.rb
module Api::Controllers::Graphql
class Show
include Api::Action
def call(params)
query_variables = params[:vairables] || {}
self.body = JSON.generate(
BlogSchema.execute(
params[:query],
variables: query_variables
)
)
end
end
end
To let Hanami know that it shouldn't render a view, we set self.body
directly inside the action.
Query the API
In order to see the API working, we need data! Fire up your hanami console and create some author and blogs.
hanami c
Now create one or more authors and save it to database via AuthorRepository:
author = Author.new(name: 'John Wayne')
AuthorRepository.persist author
Do the same for Blogs
blog = Blog.new(title: 'first blog', content: 'lorem ipsum dolor sit met', author_id: 1)
BlogRepository.persist blog
As soon as we have our data in place, we can use cURL to query our API.
curl -XGET -d 'query={ blog(id: 1) { title author { name } }}' http://localhost:2300/graphql
If all goes well you should see a response looking something like this:
{"data":{"blog":{"title":"first blog","author":{"name":"John Wayne"}}}}
Go ahead and play around with the query. If you look at the type definition for QueryType
you'll notice, that it should be possible to query for authors, too. Can you get the API to list all blog titles for a given author?
That's it. This introduction should give you a glimpse into Hanami and GraphQL. You can find more information in the section below.
Links and references
- More information about GraphQL
- The Hanami homepage
- Github repository for GraphQL-Ruby
- Marc-André Giroux's blogpost this article is based upon
- Source code from this article.