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’.
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.
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 < (obj, _, _) { AuthorRepository.find(obj.author_id) } end end
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
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.
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’ Blogpost this article is based upon
Source Code from this article.