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 end
and the app/models/post.rb to add the comments collection:
class Post < ApplicationRecord belongs_to :user has_many :comments, optional: true end
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] } end
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] } end
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 end
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 Post.all end end
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 end 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) end result end end
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.