Skip to content
Ruby JavaScript HTML Other
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
app
bin
client
config
db
lib/tasks
log
spec
vendor
.gitignore
.nvmrc
.rspec
.rubocop.yml
.ruby-gemset
.ruby-version
.travis.yml
Gemfile
Gemfile.lock
Guardfile
Procfile
Procfile.dev
README.md
Rakefile
config.ru
erd.pdf
graphql.config.json
package.json

README.md

northwind-graphql-ruby

by GraphQL Developers @ Selleo

Build Status

Demo

GitHub Logo

Initial project setup

gem install rails
rails new northwind-graphql-ruby --api --database=postgresql
echo northwind-graphql-ruby > northwind-graphql-ruby/.ruby-gemset
echo 2.4.2 > northwind-graphql-ruby/.ruby-version
cd northwind-graphql-ruby
gem install bundler

Setup data

rake db:setup

See Entity-Relationship Diagrams PDF

Setup GraphQL

Add to Gemfile

gem 'graphql'
gem 'batch-loader'

group :development do
  # ...
  # graphiql in development https://github.com/rmosolgo/graphiql-rails/issues/13#issuecomment-256256255
  gem 'sass-rails'
  gem 'uglifier'
  gem 'coffee-rails'
  gem 'graphiql-rails'
end

Then run

bundle
rails generate graphql:install

Add the engine to routes.rb

Rails.application.routes.draw do
  post "/graphql", to: "graphql#execute"
  if Rails.env.development?
    mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql"
  end
end

And start server

rails s
open http://localhost:3000/graphiql

Query GraphQL API

query { testField }

Read Introduction to GraphQL

Types

GraphQL queries begin from root types: query, mutation, and subscription (experimental).

Types describe objects and values in a system.

To query and mutate models we will define two types for each model

Types::SupplierType = GraphQL::ObjectType.define do
  name "Supplier"

  field :id, !types.ID
  field :name, types.String
  field :webpage, types.String
  field :notes, types.String
  field :errors, Types::JSONType
end

Types::SupplierInputType = GraphQL::InputObjectType.define do
  name "SupplierInput"

  argument :id, types.ID
  argument :name, types.String
  argument :webpage, types.String
  argument :notes, types.String
end

and some custom types with GraphQL::ScalarTypes like

Types::DateType = GraphQL::ScalarType.define do
  name "Date"

  coerce_input ->(value, ctx) { Date.iso8601(value) }
  coerce_result ->(value, ctx) { value.iso8601 }
end

Queries

To keep schema definition clean use GraphQL::Function

Types::QueryType = GraphQL::ObjectType.define do
  name "Query"

  field :supplier, function: Functions::FindById.new(Supplier)
  field :allSuppliers, function: Functions::FindAll.new(Supplier) do
    argument :filter, Types::SupplierFilterType
  end
end

Above defines queries

fragment supplierFields on Supplier {
  id
  name
}

{
  supplier(id: 1) {
    ...supplierFields
  }
  allSuppliers(offset: 1) {
    ...supplierFields
  }
}

Mutations

For mutations also use GraphQL::Function

Types::MutationType = GraphQL::ObjectType.define do
  name "Mutation"

  field :createSupplier, function: Functions::Create.new(Supplier) do
    argument :supplier, !Types::SupplierInputType
  end
  field :updateSupplier, function: Functions::Update.new(Supplier) do
    argument :supplier, !Types::SupplierInputType
  end
  field :deleteSupplier, function: Functions::Delete.new(Supplier)
end

Try mutations (run one mutation at a time)

fragment supplierFields on Supplier {
  id
  name
}

mutation {
  createSupplier(supplier: {name: "ACME"}) {
    ...supplierFields
    errors
  }
}

mutation {
  updateSupplier(supplier: {id: 11, name: "NewCo"}) {
    ...supplierFields
    errors
  }
}

mutation {
  deleteSupplier(id: 11) {
    ...supplierFields
  }
}

We are using Types::JSONType to get validation errors, read more on Error Handling

Relations

For one-to-one relations use Functions::HasOne that is wrapper around exAspArk/batch-loader, "Powerful tool to avoid N+1 DB or HTTP queries".

For one-to-many relations you can reuse Functions::FindAll.

Types::SupplierType = GraphQL::ObjectType.define do
  name "Supplier"
  #...

  field :contact, function: Functions::HasOne.new('id', 'contactable_id', -> (ids, obj, args, ctx) {
    Contact.where(contactable_id: ids, contactable_type: 'Supplier')
  }) do
    type Types::ContactType
  end
  field :products, function: Functions::FindAll.new(Product, -> (obj, args, ctx) {
    obj.products
  }) do
    type types[Types::ProductType]
    argument :filter, Types::ProductFilterType
  end
end

Server-side REST wrapper

You can use GraphQL::Function to write a server-side REST wrapper. Here is example for Fixer.io "Foreign exchange rates and currency conversion API"

class Functions::CurrencyRates < GraphQL::Function
  attr_reader :type

  def initialize
    @type = GraphQL::ObjectType.define do
      name "CurrencyRates"

      field :base, types.String
      field :date, Types::DateType, resolve: ->(obj, args, ctx) { Date.iso8601(obj['date']) }
      field :rates, Types::JSONType
    end
  end

  argument :date, Types::DateType
  argument :base, types.String, default_value: 'EUR'

  def call(obj, args, ctx)
    params = "#{args['date']||'latest'}?base=#{args['base']}"
    response = HTTParty.get("http://api.fixer.io/#{params}", timeout: 10)
    OpenStruct.new(response.parsed_response)
  end
end

Query

{
  currencyRates(date: "2017-10-02", base: "EUR") {
    date
    base
    rates
  }
}

Tracking Schema with GraphQL IDL Schema Dump

To update GraphQL IDL Schema dump with rake task:

rake graphql:schema

See Tracking Schema Changes With GraphQL-Ruby

TODO

  • authorization
  • testing
  • file upload
You can’t perform that action at this time.