6 reasons to stop using REST and start using GraphQL

Following up the post about a Rails API only app, lets talk about why you should not use REST in your API app.

1: too much unneeded information

Have you ever written a client application to any API? And when you did it, was there a query you needed to do that returned a lot more information than you needed?

It happens to me a lot, last week I was writing a report using PostmarkApp API and I needed to list all the events from a lot of different messages I’ve sent, and to do that, I had to download a lot of information I didn’t need about the messages, including the body of the message in plain text and HTML.

And this does not happen only to PostmarkApp, almost every API out there has the same problem deppending on what the user wants to do

2:  you are not a clairvoyant

It is almost impossible to know before hand all the great things the clients to your API will create in the future, and with REST you would need to create lots of bloated methods, or a lot of very specific methods that could be never used.

3: if you have mobile clients to your API they probably care about their bandwidth usage

I know most of the time, for a desktop computer we never think about bandwidth anymore, we send links to users to download huge files, create APIs that return a lot of information the user does not need, and we do not even care about the bandwidth usage of our servers because nowadays it is really cheap.

But when you have a mobile client, the reality is not exactly the same, and if that client is not in a first world country, they might not have a very good connection at all (yes, that is my reality 😀 )

So a mobile client, usually needs an API that returns only the needed information for that screen or for that logic, to avoid delays and other problems, like spending all your user internet data plan…

4: you will evolve and v1, v2, vx in the URL is a shitty solution

When doing REST any change in the API is usually considered a new version, to add new fields, …

In GraphQL you can just evolve the schema, and new API clients can use the new provided fields.

So there is less reasons to create shitty URLs.

5: security matters

I’m not saying here that security is not possible with REST, but since in GraphQL you can specify what fields you want in the result, it is also possible to allow some users to see one field and not see another from the same model, in the same API call.

Facebook does that a lot in their GraphQL API, with basic security you can access users email and name, to get more information you need to ask for permission, or have you application registered and in production…

What I’m saying is that GraphQL allows for a more fine grained security implementation.

6: it is easy to implement

Lets stop with the easy talk do do a simple exercise?

create a new rails app with the command:

rails new graphqasample --api --skip-test

Now we’ll add the following line to our Gemfile

gem "graphql"

And run the commands:

bundle install
rails g graphql:install

Now we are ready to start playing with GraphQL in our API app.

To start, we’ll need some rails Models, different from most rails apps, only the database schema is not enough, we’ll need to tell GraphQL what fields are available/permitted for each object.

So, lets start creating a simple “schema” for our database, with these commands:

rails g model user username:string first_name:string last_name:string birth_date:date
rails g model post user:belongs_to title:string body:text
rails g model comment post:belongs_to comment:belongs_to body:text owner:string notify_reply:boolean

And then we’ll edit the app/models/user.rb to add the posts collection:

class User < ApplicationRecord
  has_many :posts

and the app/models/post.rb to add the comments collection:

class Post < ApplicationRecord
  belongs_to :user
  has_many :comments, optional: true

And now, lets expose this “blog” using GraphQL, starting with the user model, to do that, create the file app/graphql/user_type.rb with this content:

# defines a new GraphQL type
Types::UserType = GraphQL::ObjectType.define do
  # this type is named `User`
  name 'User'
  # it has the following fields
  field :id, !types.ID
  field :username, !types.String
  field :first_name, !types.String
  field :last_name, !types.String
  field :birth_date, !types.Date
  field :posts, -> { !types[Types::PostType] }

In a graphql model we define the valid fields and the references, this user type references the PostType, so we need to define it in the file app/graphql/post_type.rb

# defines a new GraphQL type
Types::PostType = GraphQL::ObjectType.define do
  # this type is named `Post`
  name 'Post'
  # it has the following fields
  field :id, !types.ID
  field :title, !types.String
  field :body, !types.String
  field :comments, -> { !types[Types::CommentType] }

And this post type refecenres the comment type, and we need to define it in the app/graphql/comment_type.rb

# defines a new GraphQL type
Types::CommentType = GraphQL::ObjectType.define do
  # this type is named `Comment`
  name 'Comment'
  # it has the following fields
  field :id, !types.ID
  field :body, !types.String
  field :owner, !types.String

For more documentation on defining GraphQL Types, you can check the GraphQL Ruby documentation.

Now that we have all the types defined, we can enable the query to any one of them or to only one of them, but we need at least one (the User or Post are good options), and to do that, we need to edit the file app/graphql/query_type.rb

class Types::QueryType < Types::BaseObject
  # Add root-level fields here.
  # They will be entry points for queries on your schema.
  # TODO: remove me
  field :allPosts, [Types::PostType], null: false,
    description: "All User Posts In the App" 
    def all_posts

With this, we can list all posts, with or without comments, you can try it with curl, using the command lines bellow:

curl -X POST -H "Content-Type: application/json" -d '{"query": "{ allPosts{id title comments {body owner} } }"}' http://localhost:3000/graphql
curl -X POST -H "Content-Type: application/json" -d '{"query": "{ allPosts{id title } }"}' http://localhost:3000/graphql

Of course, to test this you probably need to open Rails Console first and insert some data 😀

Of course like this, it is pretty useless, since we cannot pass parameters to the query, but we can fix this easilly, we’ll do just some changes in the app/graphql/query_type.rb as follow:

class Types::QueryType < Types::BaseObject
  # Add root-level fields here.
  # They will be entry points for queries on your schema.
  # TODO: remove me
  field :allPosts, [Types::PostType], null: false,
    description: "All User Posts In the App" do
      argument :limit, Integer, required: false, default_value: 30
      argument :offset, Integer, required: false, default_value: 0
      argument :filter, String, required: false, default_value: nil
  def all_posts(limit:, offset:,filter:)
    result = Post.limit(limit).offset(offset)
    if filter
      term = "%#{filter}%"
      result = result.where("body like ? or title like ?", term, term)

This way we can add and document all parameters that ace acceptable for the query, and as of before, you can test it with curl:

curl -X POST -H "Content-Type: application/json" -d '{"query": "{ allPosts(limit: 20, filter: \"2\"){id title comments {body owner} } }"}' http://localhost:3000/graphql

This will list the first 20 posts that have the number 2 in the body or title columns.

Of course we can use nested parameters, and GraphQL also has support for editing objects, one simple post is too little to explore the possibilities, but I think it was enough to show the idea.

I’ll probably wrinte another post about nested queries and updates using GraphQL, is you think that this is useful, just leave a comment.

API only app? use rails and be happy!

Sometimes we need to create only an API, an application without a user interface, for example if you’ll have someone else built later a mobile client for that API, or even a full SPA web client, there are many reasons to build an API only app.

And since there are that many reasons, rails helps with that also, it has an option you can pass when creating a new application “–api” that will create an streamlined app to favor this kind of development.

Talking like that, it seems like a really big thing, but there aren’t that much differences.

But basically, the differences are:

  • There wont be an app/assets directory, and no asset pipeline preconfigured.
  • The rails frameworks will be expanded in the config/application.rb allowing you to comment anything you’ll not use
  • There are less gems in Gemfile (mostly the JavascriptGems are removed)
  • The ApplicationController descends from ActionController::API instead of ActionController::Base

The main difference in the controller is that it does not include by default some features like

  • Layout
  • Template rendering
  • cookies
  • sessions

Of course this will make the controller stack a lot slimmer, and faster, suitable for an API only App.

So, lets start with the command:

rails new myapissample --api

Then you can edit “config/application.rb” and maybe comment some features that you’ll not use, for example “ActionCable”

require_relative 'boot'
require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
#require "action_view/railtie"
#require "action_cable/engine"
# require "sprockets/railtie"
require "rails/test_unit/railtie"
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
module Myapissample
class Application &lt; Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 5.1
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
# Only loads a smaller set of middleware suitable for API only apps.
# Middleware like session, flash, cookies can be added back manually.
# Skip views, helpers and assets when generating a new resource.
config.api_only = true

After that you can just write your controllers as usual, access your models, …