APIs

After working through this guide you will:

  • know about the thinking behind GraphQL APIs
  • be able to configure your existing Rails App to offer a GraphQL API

Slides - use arrow keys to navigate, esc to return to page view, f for fullscreen

1 API Styles

1.1 What is an API?

API stands for "Application Programming Interface". It is a set of clearly defined methods of communication with a software component. So the objects and methods exposed by a library form an API.

In Web development the acronym API is most commonly used when the software component in question runs on a different server on the Internet and is accessed via HTTP.

1.2 SOAP, REST and GraphQL

Currently three main API styles are used on the Web:

  • SOAP, designed 1998 at Microsoft, uses XML and POST requests to make "remote procedure calls"
  • REST, described in 2000, uses different HTTP Methods and Status Messages to access "resources"
  • GraphQL, released 2015 by Facebook, uses POST requests, it's own query language and JSON

This Guide is concerned with GraphQL, there is a second guide for REST. SOAP is rearely offered with Rails, but there is a soap client in ruby.

1.3 API layer is a separate layer

Please note that any of the API styles can be used with any backend, frontend, persistance layers:

  • You can build a REST in front of a PHP backend using MongoDB as the database and use it from a frontend written with jQuery.
  • You can build a GraphQL API for a Rails backend using MySQL as the database and build the frontend with React.

That's kind of the point of an API: to allow different technologies on both sides of the API.

2 GraphQL

REST and SOAP APIs are fixed interfaces: each api call has a fixed set of arguments and returns a fixed data structure. All decisions are made when specifying the API.

GraphQL shifts some decisions to the client: through a Query Language the client can request data in the specific shape it needs, within the limits that the API sets.

2.1 Query Language

A simple example with two models: a project has many URLs. when querying through GraphQL you can request different attributes of both models:

1:n relationship in the database

Notice how the resulting JSON data mirrors the structure of the query. The query language is used for both Queries (getting data) and Mutations (changing data).

2.2 Introspection and Playground

A GraphQL API can always be queried to give information about the possible queries, mutations and types. This is called introspection.

The GraphQL Playground is a Web App that enables you to make Queries and Mutations. It uses introspection to display documentation and to offer autocompletion:

2.3 Types

A GraphQL delivers JSON, which only has objects, arrays, strings, numbers and null as types. But GraphQL itself can build a more detailed type system and check these types in queries and mutations.

2.4 How the server handles the request

  • parse the query
  • validate with schema
  • resolve data
  • convert response to JSON

3 Using Rails to build a GraphQL API

3.1 Setup

To add a GraphQL API to an existing Rails app you need just two gems:

gem 'graphql'
group :development do
  gem 'graphql-rails-generators'
end

3.1.1 generator

The rest of the setup is handled by a generator added by the graphql gem:

rails generate graphql:install
       exist  app/graphql/types
      create  app/graphql/types/.keep
      create  app/graphql/portfolio_relaunch_schema.rb
      create  app/graphql/types/base_object.rb
      create  app/graphql/types/base_argument.rb
      create  app/graphql/types/base_field.rb
      create  app/graphql/types/base_enum.rb
      create  app/graphql/types/base_input_object.rb
      create  app/graphql/types/base_interface.rb
      create  app/graphql/types/base_scalar.rb
      create  app/graphql/types/base_union.rb
      create  app/graphql/types/query_type.rb
add_root_type  query
      create  app/graphql/mutations
      create  app/graphql/mutations/.keep
      create  app/graphql/mutations/base_mutation.rb
      create  app/graphql/types/mutation_type.rb
add_root_type  mutation
      create  app/controllers/graphql_controller.rb
       route  post "/graphql", to: "graphql#execute"
     gemfile  graphiql-rails
       route  graphiql-rails
Gemfile has been modified, make sure you `bundle install`

3.1.2 playground is available

After running bundle and restarting the server you can access the graphql playground at http://localhost:3000/graphiql: in the left pane you can enter a query, run it, and see the result in the middle pane. The right pane contains documentation that was created automatically.

GraphQL Playground

We have no Queries to run yet.

3.2 Generating a Type from a Model

As a first step we can use a generator to create a type from an existing model:

rails generate gql:model_type Project
      create  app/graphql/types/project_type.rb

Now we can edit the type to fit our needs, for example we can remove attributes that should never be public:

module Types
  class ProjectType < Types::BaseObject
    field :id, GraphQL::Types::ID, null: false
    field :title, String, null: false
    field :publicationdate, GraphQL::Types::ISO8601Date, null: false
    field :membership, Int, null: true
    field :created_at, GraphQL::Types::ISO8601DateTime, null: false
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
    field :slug, String, null: false
  end
end

3.3 Defining a Query

The next level up we come to the query. There is already a dummy Query in app/graphql/types/query_type.rb

module Types
  class QueryType < Types::BaseObject
    # Add root-level fields here.
    # They will be entry points for queries on your schema.

    # TODO: remove me
    field :test_field, String, null: false,
      description: "An example field added by the generator"
    def test_field
      "Hello World!"
    end
  end
end

3.3.1 Declare the Query and implement the resolver

We can replace this with a query that returns a list of all projects:

module Types
  class QueryType < Types::BaseObject
    field :all_projects, [ProjectType],
      null: false,
      description: "a list of all publicly visible projects"
    def all_projects
      Project.public.all
    end
  end
end

We can now run the query in the playground. Your query will be autocompleted.

3.3.2 Queries with arguments

If a query needs arguments you have do declare them in the fields declaration and then handle them in the method:

    field :project, ProjectType, null: true do
      argument :id, ID, required: true
    end
    def project(id:)
      Project.visible.find(id)
    end

3.4 Relationships between models

3.4.1 Create type for a second model

1:n relationship in the database

generate the UrlType automatically from the model:

module Types
  class UrlType < Types::BaseObject
    field :id, GraphQL::Types::ID, null: false
    field :title, String, null: true
    field :url, String, null: true
    field :url_type, String, null: true
    field :created_at, GraphQL::Types::ISO8601DateTime, null: true
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: true
  end
end

3.4.2 create queries for the second model

add the queries all_urls and url to app/graphql/types/query_type.rb

now you can use those queries in the playground

3.4.3 add field for relationship to type

add the field urls to app/graphql/types/project_type.rb:

module Types
  class ProjectType < Types::BaseObject
    field :id, GraphQL::Types::ID, null: false
    field :title, String, null: false
    field :publicationdate, GraphQL::Types::ISO8601Date, null: false
    field :membership, Int, null: true
    field :created_at, GraphQL::Types::ISO8601DateTime, null: false
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
    field :slug, String, null: false
    field :urls, [ UrlType ], null: false
  end
end

now you can query for Urls through their project:

query a 1:n relationship with graphql

3.4.4 inverse relationship

what would you need to do, to make a query from Url to Project possible?

{
  url(id:2077){
    id
    url
    urlType
    title
    project {
      title
    }
  }
}

3.4.5 implementation of the inverse relationship

Answer: add a field project to UrlType:

module Types
  class UrlType < Types::BaseObject
    field :id, GraphQL::Types::ID, null: false
    field :title, String, null: true
    field :url, String, null: true
    field :url_type, String, null: true
    field :created_at, GraphQL::Types::ISO8601DateTime, null: true
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: true
    field :project, ProjectType, null: false
  end
end

query a 1:n relationship with graphql (reverse)

3.5 More Types

In Ruby and JavaScript we often use Strings to store all kinds of values. In the example above, Url.url_type only has three possible values: 'Link','Repository','Award'.

GraphQL comes with an Enum Type that we can use for that

3.5.1 define the Enum Type:

rails g graphql:enum UrlTypeEnum Link Repository Award
      create  app/graphql/types/url_type_enum_type.rb
module Types
  class UrlTypeEnumType < Types::BaseEnum
    value "Link"
    value "Repository"
    value "Award"
  end
end

3.5.2 using the Enum Type

This can now be used in UrlType:

module Types
  class UrlType < Types::BaseObject
    field :id, GraphQL::Types::ID, null: false
    field :title, String, null: true
    field :url, String, null: true
    field :url_type, UrlTypeEnumType, null: true
    field :created_at, GraphQL::Types::ISO8601DateTime, null: true
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: true
    field :project, ProjectType, null: false
  end
end

3.5.3 Types: uses and limitations

The data returned by a query is still JSON, and cannot contain enums, only Strings.

querying an enum gives a string

In the documentation you can see that only three Strings are valid. The GraphQL API will validate this both in Queries and in Mutations.

4 A lot more to learn

This guide has not touched on:

  • mutations
  • filtering, searching
  • pagination
  • resolving graphql queries that are not directly mapped to models
  • ...

4.1 See Also