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:
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.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.
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.1 Create type for a second model
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:
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
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.
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
- GraphQL Guides: Ruby
- Pragmatic Tutorial: GraphQL? (see also part 2 + 3)
- Apollo Client + Server in JavaScript
- API Platform to build REST + GraphQL APIs in PHP
- Analyzing the Twitch.tv GraphQL API