{"id":13483267,"url":"https://github.com/ziprandom/graphql-crystal","last_synced_at":"2025-03-27T14:31:13.061Z","repository":{"id":45752881,"uuid":"86385550","full_name":"ziprandom/graphql-crystal","owner":"ziprandom","description":"a graphql implementation for crystal","archived":false,"fork":false,"pushed_at":"2020-07-01T01:39:51.000Z","size":688,"stargazers_count":215,"open_issues_count":9,"forks_count":16,"subscribers_count":7,"default_branch":"master","last_synced_at":"2024-10-30T17:47:44.438Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Crystal","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ziprandom.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-03-27T21:26:42.000Z","updated_at":"2024-03-13T03:54:19.000Z","dependencies_parsed_at":"2022-09-24T05:51:15.262Z","dependency_job_id":null,"html_url":"https://github.com/ziprandom/graphql-crystal","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziprandom%2Fgraphql-crystal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziprandom%2Fgraphql-crystal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziprandom%2Fgraphql-crystal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziprandom%2Fgraphql-crystal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ziprandom","download_url":"https://codeload.github.com/ziprandom/graphql-crystal/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245863058,"owners_count":20684779,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T17:01:09.514Z","updated_at":"2025-03-27T14:31:12.600Z","avatar_url":"https://github.com/ziprandom.png","language":"Crystal","readme":"# graphql-crystal [![Build Status](https://api.travis-ci.org/ziprandom/graphql-crystal.svg)](https://travis-ci.org/ziprandom/graphql-crystal)\n\n\nAn implementation of [GraphQL](http://graphql.org/learn/) for the crystal programming language inspired by [graphql-ruby](https://github.com/rmosolgo/graphql-ruby) \u0026 [go-graphql](https://github.com/playlyfe/go-graphql) \u0026 [graphql-parser](https://github.com/graphql-dotnet/parser).\n\nThe library is in beta state atm. Should already be usable but expect to find bugs (and open issues about them). pull-requests, suggestions \u0026 criticism are very welcome!\n\nFind the api docs [here](https://ziprandom.github.io/graphql-crystal/).\n\n## Installation\n\nAdd this to your application's `shard.yml`:\n\n```yaml\ndependencies:\n  graphql-crystal:\n    github: ziprandom/graphql-crystal\n```\n\n## Usage\n\nComplete source [here](example/simple_example.cr).\n\nGiven this simple domain model of users and posts\n\n```cr\nclass User\n  property name\n  def initialize(@name : String); end\nend\n\nclass Post\n  property :title, :body, :author\n  def initialize(@title : String, @body : String, @author : User); end\nend\n\nPOSTS = [] of Post\nUSERS = [User.new(\"Alice\"), User.new(\"Bob\")]\n```\n\nWe can instantiate a GraphQL schema directly from a graphql schema definition string\n\n```cr\nschema = GraphQL::Schema.from_schema(\n  %{\n    schema {\n      query: QueryType,\n      mutation: MutationType\n    }\n\n    type QueryType {\n      posts: [PostType]\n      users: [UserType]\n      user(name: String!): UserType\n    }\n\n    type MutationType {\n      post(post: PostInput) : PostType\n    }\n\n    input PostInput {\n      author: String!\n      title: String!\n      body: String!\n    }\n\n    type UserType {\n      name: String\n      posts: [PostType]\n    }\n\n    type PostType {\n      author: UserType\n      title: String\n      body: String\n    }\n  }\n)\n```\n\nThen we create the backing types by including the ```GraphQL::ObjectType``` and defining the fields using the ```field``` macro\n\n```cr\n# reopening User and Post class\nclass User\n  include GraphQL::ObjectType\n\n  # defaults to the method of\n  # the same name without block\n  field :name\n\n  field :posts do\n    POSTS.select \u0026.author.==(self)\n  end\nend\n\nclass Post\n  include GraphQL::ObjectType\n  field :title\n  field :body\n  field :author\nend\n```\n\nNow we define the top level queries\n\n```cr\n# extend self when using a module or a class (not an instance)\n# as the actual Object\n\nmodule QueryType\n  include GraphQL::ObjectType\n  extend self\n\n  field :users do\n    USERS\n  end\n\n  field :user do |args|\n    USERS.find( \u0026.name.==(args[\"name\"].as(String)) ) || raise \"no user by that name\"\n  end\n\n  field :posts do\n    POSTS\n  end\nend\n\nmodule MutationType\n  include GraphQL::ObjectType\n  extend self\n\n  field :post do |args|\n\n    user = USERS.find \u0026.name.==(\n      args[\"post\"].as(Hash)[\"author\"].as(String)\n    )\n    raise \"author doesn't exist\" unless user\n\n    (\n      POSTS \u003c\u003c Post.new(\n        args[\"post\"].as(Hash)[\"title\"].as(String),\n        args[\"post\"].as(Hash)[\"body\"].as(String),\n        user\n      )\n    ).last\n  end\nend\n```\n\nFinally set the top level Object Types on the schema\n\n```cr\nschema.query_resolver = QueryType\nschema.mutation_resolver = MutationType\n```\n\nAnd we are ready to run some tests\n\n```cr\ndescribe \"my graphql schema\" do\n  it \"does queries\" do\n    schema.execute(\"{ users { name posts } }\")\n      .should eq ({\n                    \"data\" =\u003e {\n                      \"users\" =\u003e [\n                        {\n                          \"name\" =\u003e \"Alice\",\n                          \"posts\" =\u003e [] of String\n                        },\n                        {\n                          \"name\" =\u003e \"Bob\",\n                          \"posts\" =\u003e [] of String\n                        }\n                      ]\n                    }\n                  })\n  end\n\n  it \"does mutations\" do\n\n    mutation_string = %{\n      mutation post($post: PostInput) {\n        post(post: $post) {\n          author {\n            name\n            posts { title }\n          }\n          title\n          body\n        }\n      }\n    }\n\n    payload = {\n      \"post\" =\u003e {\n        \"author\" =\u003e  \"Alice\",\n        \"title\" =\u003e \"the long and windy road\",\n        \"body\" =\u003e \"that leads to your door\"\n      }\n    }\n\n    schema.execute(mutation_string, payload)\n      .should eq ({\n                    \"data\" =\u003e {\n                      \"post\" =\u003e {\n                        \"title\" =\u003e \"the long and windy road\",\n                        \"body\" =\u003e \"that leads to your door\",\n                        \"author\" =\u003e {\n                          \"name\" =\u003e \"Alice\",\n                          \"posts\" =\u003e [\n                            {\n                              \"title\" =\u003e \"the long and windy road\"\n                            }\n                          ]\n                        }\n                      }\n                    }\n                  })\n  end\nend\n```\n\n### Automatic Parsing of JSON Query \u0026 Mutation Variables into InputType Structs\n\nTo 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\n\n```cr\nstruct PostInput \u003c GraphQL::Schema::InputType\n  JSON.mapping(\n    author: String,\n    title: String,\n    body: String\n  )\nend\n```\n\nand register it in the schema like:\n\n```cr\nschema.add_input_type(\"PostInput\", PostInput)\n```\n\nNow the argument `post` which is expected to be a GraphQL InputType `PostInput` will be automatically parsed into a crystal `PostInput`-struct. Thus the code in the `post` mutation callback becomes more simple:\n\n```cr\nmodule MutationType\n  include GraphQL::ObjectType\n  extend self\n\n  field :post do |args|\n    input = args[\"post\"].as(PostInput)\n\n    author = USERS.find \u0026.name.==(input.author) ||\n           raise \"author doesn't exist\"\n\n    POSTS \u003c\u003c Post.new(input.title, input.body, author)\n    POSTS.last\n  end\nend\n```\n\n### Custom Context Types\n\nCustom context types can be used to pass additional information to the object type's field resolves. An example can be found [here](spec/support/custom_context_schema.cr).\n\nA custom context type should inherit from `GraphQL::Schema::Context` and therefore be initialized with the served schema and a max_depth.\n\n```cr\nGraphQL::Schema::Schema#execute(query_string, query_arguments = nil, context = GraphQL::Schema::Context.new(self, max_depth))\n```\naccepts a context type as its third argument.\n\nField resolver callbacks on object types (including top level query \u0026 mutation types) get called with the context as their second argument:\n```cr\nfield :users do |args, context|\n  # casting to your custom type\n  # is necessary here\n  context = context.as(CustomContext)\n  unless context.authenticated\n    raise \"Authentication Error\"\n  end\n  ...\nend\n```\n\n### Serving over HTTP\n\nFor an example of how to serve a schema over a webserver([kemal](https://github.com/kemalcr/kemal)) see [kemal-graphql-example](https://github.com/ziprandom/kemal-graphql-example).\n\n## Development\n\nrun tests with\n\n```\ncrystal spec\n```\n\n## Contributing\n\n1. Fork it ( https://github.com/ziprandom/graphql-crystal/fork )\n2. Create your feature branch (git checkout -b my-new-feature)\n3. Commit your changes (git commit -am 'Add some feature')\n4. Push to the branch (git push origin my-new-feature)\n5. Create a new Pull Request\n\n## Contributors\n\n- [ziprandom](https://github.com/ziprandom)  - creator, maintainer\n","funding_links":[],"categories":["Libraries","Crystal","Framework Components","Implementations","\u003ca name=\"Crystal\"\u003e\u003c/a\u003eCrystal"],"sub_categories":["Crystal Libraries","Crystal"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fziprandom%2Fgraphql-crystal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fziprandom%2Fgraphql-crystal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fziprandom%2Fgraphql-crystal/lists"}