GRAPHQL on RAILS

 

Mexico on Rails, Mexico City, Mexico

May 14, 2019

 

  • Brazilian
  • Bachelor and Master on Computer Science (UFBA)
  • Ruby On Rails since 2008
  • http://ca.ios.ba
  • Meedan Software Engineer since 2011      
     
  • San Francisco - California
     
  • http://meedan.com

Check

Social media fact-checking

2012 - today

github.com/meedan/checkdesk

   github.com/meedan/check-api

   github.com/meedan/check-web

   github.com/meedan/lapis

github.com/meedan/generator-keefer

Backend

Frontend

Ruby On Rails template

Yeoman generator

graphql

react.js

relay

ruby on rails

graphql

react.js

relay

ruby on rails

BACKEND

FRONTEND

GraphQL

"GraphQL is a query language for your API, and a server-side runtime for executing queries by using a type system you define for your data"

 

 

REST

Media

Comment

Tag

1

*

User

*

*

*

1

GET /api/v1/medias/1
GET /api/v1/medias/1/comments
GET /api/v1/medias/1/tags
GET /api/v1/medias/1/comments/1
GET /api/v1/users/1?fields=avatar,name
GET /api/v1/users/2?fields=avatar,name
GET /api/v1/users/3?fields=avatar,name
...

Reusable endpoints

GET /api/v1/medias/1?include=comments&count=5

GET /api/v1/medias/1?include=comments,tags
    &comments_count=5&tags_count=5

GET /api/v1/medias/1?fields=comments(text,date)
    &tags(tag)


...


GET /api/v1/media_and_comments/1
GET /api/v1/media_comments_and_tags/1
GET /api/v1/media_comments_tags_and_users/1
GET /api/v1/medias/1?include=comments&count=5

GET /api/v1/medias/1?include=comments,tags
    &comments_count=5&tags_count=5

GET /api/v1/medias/1?fields=comments(text,date)
    &tags(tag)


...


GET /api/v1/media_and_comments/1
GET /api/v1/media_comments_and_tags/1
GET /api/v1/media_comments_tags_and_users/1

Too many requests!

GraphQL

One endpoint to rule them all

POST /graphql

POST /api/graphql?query=
{
  media(id: 1) {
    title
    embed
    tags(first: 3) {
      tag
    }
    comments(first: 5) {
      created_at
      text
      user {
        name,
        avatar
      }
    }
  }
}
POST /api/graphql?query=
{
  media(id: 1) {
    title
    embed
    tags(first: 3) {
      tag
    }
    comments(first: 5) {
      created_at
      text
      user {
        name,
        avatar
      }
    }
  }
}

Media

Comment

Tag

1

*

User

*

*

*

1

~

POST /api/graphql?query=
{
  media(id: 1) {
    title
    embed
    tags(first: 3) {
      tag
    }
    comments(first: 5) {
      created_at
      text
      user {
        name,
        avatar
      }
    }
  }
}
{
  "media": {
    "title": "Avangers Hulk Smash",
    "embed": "<iframe src=\"...\"></iframe>",
    "tags": [
      { "tag": "avengers" },
      { "tag": "operation" }
    ],
    "comments": [
      {
        "text": "This is true",
        "created_at": "2016-09-18 15:04:39",
        "user": {
          "name": "Ironman",
          "avatar": "http://[...].png"
        }
      },
      ...
    ]
  }
}

GraphQL

Ruby On Rails

Creating a new Rails API with GraphQL support

$ git clone https://github.com/meedan/lapis.git
$ rails new <name> -m <path to lapis_template.rb>
  • No view (headless API)
  • REST & GraphQL
  • Swagger UI & GraphiQL
  • Docker
  • Client gem generation
  • Webhooks & tokens

Adding GraphQL support to existing application

gem 'graphql'
gem 'graphql-relay'

Gemfile

class GraphqlController < BaseApiController
  def create
    vars = params[:variables] || {}
    query = GraphQL::Query.new(RelayOnRailsSchema, params[:query], variables: vars)
    render json: query.result
  end
end

app/controllers/graphql_controller.rb (add route too)

RelayOnRailsSchema = GraphQL::Schema.new(query: QueryType, mutation: MutationType)

config/initializers/relay.rb

type Media {
  title: String
  embed: String
  user: User
  comments: [Comment]
}
# "Type" to differentiate from models
MediaType = GraphQL::ObjectType.define do
  name 'Media'
  description 'Media type'
  field :title, types.String
  field :embed, types.String
  field :user do
    type UserType
    resolve -> (media, _args, _ctx) {
      media.user
    }
  end
  connection :comments, ->{ CommentType.connection_type }
  do
    argument :user_id, types.Int

    resolve -> (media, args, ctx) {
      media.comments(args[:user_id], ctx[:current_user])
    }
  end
end

Defining a GraphQL type

app/graph/types/media_type.rb

# "Type" to differentiate from models
# MediaType = GraphQL::ObjectType.define do
#   name 'Media'
#   description 'Media type'
    field :title, types.String
    field :embed, types.String
#   field :user do
#     type UserType
#     resolve -> (media, _args, _ctx) {
#       media.user
#     }
#   end
#   connection :comments, ->{ CommentType.connection_type }
#   do
#     argument :user_id, types.Int
#
#     resolve -> (media, args, ctx) {
#       media.comments(args[:user_id], ctx[:current_user])
#     }
#   end
# end

Defining a GraphQL type

By default, fields resolve to model instance methods

# "Type" para diferenciar dos models
# MediaType = GraphQL::ObjectType.define do
#   name 'Media'
#   description 'Media type'
#   field :title, types.String
#   field :embed, types.String
#   field :user do
#     type UserType
#     resolve -> (media, _args, _ctx) {
#       media.user
#     }
#   end
 connection :comments, ->{ CommentType.connection_type } 
 do
   argument :user_id, types.Int

   resolve -> (media, args, ctx) {
     media.comments(args[:user_id], ctx[:current_user])
   }
 end
# end

Defining a GraphQL type

Relationships reference connections of other GraphQL types, can receive arguments and have access to the context

Mutations

mutation { 
  createMedia(
    input: {
      url: "http://youtu.be/7a_insd29fk"
      clientMutationId: "1"
    }
   )
   {
     media {
       id
     }
   }
}

Mutations make changes on your server side.

CRUD:

Queries: Read

Mutations:

  • Create
  • Update
  • Delete
# mutation { 
     createMedia(
#     input: {
        url: "http://youtu.be/7a_insd29fk"
#       clientMutationId: "1"
#     }
#    )
     {
       media {
         id
       }
     }
# }

Mutation name

Input parameters

Desired output

CreateMedia = GraphQL::Relay::Mutation.define do
  input_field :url, !types.String

  return_field :media, MediaType

  resolve -> (inputs, ctx) {
    media = Media.new
    media.url = inputs[:url]
    media.save!
  }
end

On Rails, a mutation must define the input fields, return fields and a resolution method (what the mutation actually does)

Gracias! :)

slides.com/caiosba/mexico19

http://ca.ios.ba

@caiosba