Need help with graphql-crystal?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

210 Stars 17 Forks MIT License 148 Commits 9 Opened issues


a graphql implementation for crystal

Services available


Need anything else?

Contributors list

graphql-crystal Build Status

An implementation of GraphQL for the crystal programming language inspired by graphql-ruby & go-graphql & graphql-parser.

The library is in beta state atm. Should already be usable but expect to find bugs (and open issues about them). pull-requests, suggestions & criticism are very welcome!

Find the api docs here.


Add this to your application's

    github: ziprandom/graphql-crystal


Complete source here.

Given this simple domain model of users and posts

class User
  property name
  def initialize(@name : String); end

class Post property :title, :body, :author def initialize(@title : String, @body : String, @author : User); end end

POSTS = [] of Post USERS = ["Alice"),"Bob")]

We can instantiate a GraphQL schema directly from a graphql schema definition string

schema = GraphQL::Schema.from_schema(
    schema {
      query: QueryType,
      mutation: MutationType

type QueryType {
  posts: [PostType]
  users: [UserType]
  user(name: String!): UserType

type MutationType {
  post(post: PostInput) : PostType

input PostInput {
  author: String!
  title: String!
  body: String!

type UserType {
  name: String
  posts: [PostType]

type PostType {
  author: UserType
  title: String
  body: String

} )

Then we create the backing types by including the

and defining the fields using the
# reopening User and Post class
class User
  include GraphQL::ObjectType

defaults to the method of

the same name without block

field :name

field :posts do &.author.==(self) end end

class Post include GraphQL::ObjectType field :title field :body field :author end

Now we define the top level queries

# extend self when using a module or a class (not an instance)
# as the actual Object

module QueryType include GraphQL::ObjectType extend self

field :users do USERS end

field :user do |args| USERS.find( &.name.==(args["name"].as(String)) ) || raise "no user by that name" end

field :posts do POSTS end end

module MutationType include GraphQL::ObjectType extend self

field :post do |args|

user = USERS.find &.name.==(
raise "author doesn't exist" unless user

  POSTS <<

end end

Finally set the top level Object Types on the schema

schema.query_resolver = QueryType
schema.mutation_resolver = MutationType

And we are ready to run some tests

describe "my graphql schema" do
  it "does queries" do
    schema.execute("{ users { name posts } }")
      .should eq ({
                    "data" => {
                      "users" => [
                          "name" => "Alice",
                          "posts" => [] of String
                          "name" => "Bob",
                          "posts" => [] of String

it "does mutations" do

mutation_string = %{
  mutation post($post: PostInput) {
    post(post: $post) {
      author {
        posts { title }

payload = {
  "post" => {
    "author" =>  "Alice",
    "title" => "the long and windy road",
    "body" => "that leads to your door"

schema.execute(mutation_string, payload)
  .should eq ({
                "data" => {
                  "post" => {
                    "title" => "the long and windy road",
                    "body" => "that leads to your door",
                    "author" => {
                      "name" => "Alice",
                      "posts" => [
                          "title" => "the long and windy road"

end end

Automatic Parsing of JSON Query & Mutation Variables into InputType Structs

To ease working with input parameters custom structs can be registered to be instantiated from the json params of query and mutation requests. Given the schema from above one can define a PostInput struct as follows

struct PostInput < GraphQL::Schema::InputType
    author: String,
    title: String,
    body: String

and register it in the schema like:

schema.add_input_type("PostInput", PostInput)

Now the argument

which is expected to be a GraphQL InputType
will be automatically parsed into a crystal
-struct. Thus the code in the
mutation callback becomes more simple:
module MutationType
  include GraphQL::ObjectType
  extend self

field :post do |args| input = args["post"].as(PostInput)

author = USERS.find &amp;.name.==( ||
       raise "author doesn't exist"

POSTS &lt;&lt;, input.body, author)

end end

Custom Context Types

Custom context types can be used to pass additional information to the object type's field resolves. An example can be found here.

A custom context type should inherit from

and therefore be initialized with the served schema and a max_depth.
GraphQL::Schema::Schema#execute(query_string, query_arguments = nil, context =, max_depth))

accepts a context type as its third argument.

Field resolver callbacks on object types (including top level query & mutation types) get called with the context as their second argument:

field :users do |args, context|
  # casting to your custom type
  # is necessary here
  context =
  unless context.authenticated
    raise "Authentication Error"

Serving over HTTP

For an example of how to serve a schema over a webserver(kemal) see kemal-graphql-example.


run tests with

crystal spec


  1. Fork it ( )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request


We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.