Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The GraphQL Way: A new path for JSON APIs

The GraphQL Way: A new path for JSON APIs

Have you written JSON APIs in Rails a few times? Even if you’re escaped implementing a “render: :json” call, some day soon you’ll need to get JSON data out to a native client or your front-end framework. Your Rails apps might change, but the pitfalls for JSON APIs stay the same: They’re hard to discover, difficult to change from the client side, and documenting them is a pain. For your next app, or your current one, let’s consider GraphQL. I'll show you how to implement it in your app and offer real-world advice from making it work in an existing Rails app.

https://github.com/chatterbugapp/cacheql

Nick Quaranto

April 17, 2018
Tweet

More Decks by Nick Quaranto

Other Decks in Programming

Transcript

  1. Returns JSON! { "data": { "currentUser": { "id": "106", "login":

    "qrush", "email": "[email protected]", "isStudent": true, "updatedAt": "2018-03-13T15:20:14Z" } } }
  2. No problem! { "data": { "currentUser": { "login": "qrush", "upcomingAppointments":

    [ { "startTime": "2018-03-25T04:00:00Z", "language": { "code": "de" } } ] } } }
  3. Structure $ tree app/graphql/ app/graphql/ ├── chatterbug_schema.rb ├── mutations │

    ├── mutation_type.rb │ └── update_user.rb └── types ├── language_type.rb └── query_type.rb
  4. Expose it Types::QueryType = GraphQL::ObjectType.define do field :germanLanguage do type

    Types::LanguageType resolve -> (obj, args, ctx) do Language.find_by(code: "de") end end end
  5. A type Types::LanguageType = GraphQL::ObjectType.define do name "Language" field :name,

    types.String, description: "Name of the language” end
  6. A mutation Mutations::MutationType = GraphQL::ObjectType.define do field :updateUser, Types::UserType do

    argument :user, Types::UserInputType resolve -> (obj, args, ctx) { user = ctx[:current_user] user.update_attributes!(args.to_h['user']) user } end end
  7. Functions # in app/graphql/mutations/update_user.rb class Mutations::UpdateUser < GraphQL::Function argument :user,

    Types::UserInputType def call(input, args, ctx) user = ctx[:current_user] user.update_attributes!(args.to_h[‘user']) user end end
  8. Function tests require 'test_helper' class UpdateUserTest < ActiveSupport::TestCase test "can

    change info" do user = users(:scott) function = Mutations::UpdateUser.new returned_user = function.call(nil, {'user' => {'login' => 'scott'}}, {current_user: user}) assert_equal user.id, returned_user.id assert_equal 'scott', user.reload.login end end
  9. Integration tests require 'test_helper' class GraphQLTest < ActionDispatch::IntegrationTest test "current

    token is required" do post "/api/graphql", as: :json, params: { query: <<~QUERY { currentUser { email } } QUERY } assert_response :unauthorized end end
  10. Test auth! test "requesting current user with token" do post

    "/api/graphql", as: :json, params: { query: <<~QUERY { currentUser { email } } QUERY }, headers: { 'HTTP_AUTHORIZATION': ActionController::HttpAuthentication::Token .encode_credentials(user_tokens(:scott).token) } assert_response :success current_user = JSON.parse(response.body) .dig(“data", "currentUser") assert_equal "[email protected]", current_user["email"] end
  11. CacheQL # Released today! # All the goodies from this

    talk # Gemfile gem 'cacheql' https:/ /github.com/chatterbugapp/cacheql
  12. Foreign Keys resolve -> (obj, args, context) { RecordLoader.for(Language) .load(args["id"])

    } https:/ /github.com/Shopify/graphql-batch/blob/master/examples/record_loader.rb https:/ /github.com/rmosolgo/graphql-batch-example/blob/master/good_schema/find_loader.rb
  13. Polymorphic Keys # belongs_to :respondable, # polymorphic: true resolve ->

    (obj, args, context) { PolyKeyLoader.for(obj, :respondable) .load(obj.respondable) } https:/ /github.com/rmosolgo/graphql-batch-example/blob/master/good_schema/polymorphic_key_loader.rb
  14. Cache results resolve CacheQL -> (obj, args, ctx) { #

    expensive operation # cached on obj.cache_key } https:/ /github.com/rmosolgo/graphql-batch-example/blob/master/good_schema/polymorphic_key_loader.rb
  15. Rails.logger [CacheQL::Tracing] User.displayLanguage took 7.591ms [CacheQL::Tracing] User.createdAt took 0.117ms [CacheQL::Tracing]

    User.intercomHash took 0.095ms [CacheQL::Tracing] User.id took 0.09ms [CacheQL::Tracing] User.friendlyTimezone took 0.087ms [CacheQL::Tracing] User.utmContent took 0.075ms [GraphQL::Tracing] User.timezone took 0.048ms [CacheQL::Tracing] User.email took 0.046ms [CacheQL::Tracing] User.name took 0.042ms [CacheQL::Tracing] Query.currentUser took 0.041ms